跳转到主要内容

Splinter辅助工具,用于从模型创建爬虫

项目描述

使用Scrapy选择器创建爬虫
============================================

[![构建
状态](https://travis-ci.org/rochacbruno/spider_model.png)](https://travis-ci.org/rochacbruno/spider_model)

[![PyPi版本](https://pypip.in/v/spider_model/badge.png)](https://pypi.python.org/pypi/spider_model/)
[![PyPi下载](https://pypip.in/d/spider_model/badge.png)](https://pypi.python.org/pypi/spider_model/)

## 什么是Splinter?

Splinter是一个开源工具,用于使用Python测试Web应用程序。它允许您自动化浏览器操作,例如访问URL和与其项目交互。

http://splinter.cobrateam.info/


## 什么是splinter_model?

它是[scrapy_model](http://github.com/rochacbruno/scrapy_model)的克隆版,但使用Splinter作为引擎,而不是Scrapy,因此允许抓取JavaScript网站。


## TODO

所有内容都在待办事项列表中,因此请不要在它发布到PyPI之前使用它。

### 要求

此模块应保持与scrapy_model相同的API,至少支持CSSField、XPathField、处理器、验证器、多查询和解析方法。

还应实现一个与JavaScript交互的层。

### 当前状态:预_alpha-dev

它只是创建爬虫的辅助工具,使用Scrapy选择器允许您通过CSS或XPath选择元素,并通过模型(就像ORM模型一样)来结构化您的爬虫,并通过`populate`方法连接到ORM模型。

导入BaseFetcherModel、CSSField或XPathField(您可以使用两者)

```python
from spider_model import BaseFetcherModel, CSSField
```

前往您想要抓取的网页,使用Chrome开发者工具或Firebug找出CSS路径,然后考虑您想要从某个页面获取以下片段。

```html
<span id="person">Bruno Rocha <a href="http://brunorocha.org">website</a></span>
```

```python
class MyFetcher(BaseFetcherModel)
name = CSSField('span#person')
website = CSSField('span#person a')
# XPathField('//xpath_selector_here')
```

字段可以接收 ``auto_extract=True`` 参数,在调用 parse 或处理器之前自动从选择器提取值。您还可以传递 ``takes_first=True``,这将自动提取并尝试获取结果的第一元素,因为Scrapy选择器返回一个匹配元素列表。


### 单个字段中的多个查询

您可以为单个字段使用多个查询

```python
name = XPathField(
['//*[@id="8"]/div[2]/div/div[2]/div[2]/ul',
'//*[@id="8"]/div[2]/div/div[3]/div[2]/ul']
)
```

在这种情况下,解析将尝试通过第一个查询来获取数据,如果找到匹配项,则返回,否则将尝试后续的查询,直到找到匹配项,或者返回一个空选择器。

#### 通过查询验证器找到最佳匹配

如果您想运行多个查询并验证最佳匹配,可以传递一个验证器函数,该函数将接受Scrapy选择器并返回一个布尔值。

例如,假设您有上面定义的 "name" 字段,并且您想验证每个查询以确保其中包含一个文本为 "Schblaums" 的 'li'。

```python

def has_schblaums(selector)
for li in selector.css('li'): # takes each <li> inside the ul selector
li_text = li.css('::text').extract() # 提取文本
if "Schblaums" in li_text: # 检查是否存在 "Schblaums"
return True # 这个选择器有效!
return False # 无效查询,取下一个或默认值

class Fetcher(....)
name = XPathField(
['//*[@id="8"]/div[2]/div/div[2]/div[2]/ul',
'//*[@id="8"]/div[2]/div/div[3]/div[2]/ul'],
query_validator=has_schblaums,
default="undefined_name" # 可选
)
```

在上面的示例中,如果两个查询都无效,"name" 字段将填充为 empty_selector,或者默认参数中定义的值。

> **注意:**如果字段有 "default" 且所有匹配器都失败,则默认值将传递给 "processor" 和 "parse_" 方法。

每个名为 ``parse_<field>`` 的方法将在获取每个字段的值之后运行。

```python
def parse_name(self, selector)
# 这里 selector 是 'span#person' 的 Scrapy 选择器
name = selector.css('::text').extract()
return name

def parse_website(self, selector)
# 这里 selector 是 'span#person a' 的 Scrapy 选择器
website_url = selector.css('::attr(href)').extract()
return website_url

```


定义后需要运行爬虫


```python

fetcher = Myfetcher(url='http://.....') # 可选使用 cached_fetch=True 在 Redis 上缓存请求
fetcher.parse()
```

现在可以迭代 fetcher 中的 _data、_raw_data 和属性

```python
>>> fetcher.name
<CSSField - name - Bruno Rocha>
>>> fetcher.name.value
Bruno Rocha
>>> fetcher._data
{"name": "Bruno Rocha", "website": "http://brunorocha.org"}
```

您可以填充一些对象

```python
>>> obj = MyObject()
>>> fetcher.populate(obj) # 字段可选

>>> obj.name
Bruno Rocha
```

如果您不想在类中显式定义每个字段,可以使用json文件来自动化过程

```python
class MyFetcher(BaseFetcherModel)
""" 将从json加载 """

fetcher = MyFetcher(url='http://.....')
fetcher.load_mappings_from_file('path/to/file.json')
fetcher.parse()
```

在这种情况下,file.json 应该是

```json
{
"name": {"css": "span#person"},
"website": {"css": "span#person a"}
}
```

如果您更愿意使用xpath选择,可以使用 ``{"xpath": "..."}``


### 解析和处理器

有两种方式可以转换或规范化每个字段的值。

#### 处理器

处理器是一个函数,或者是一系列按给定顺序调用的函数,它将针对字段值调用,它接收raw_selector或值,具体取决于auto_extract和take_first参数。

它可以用于归一化、清理、转换等。

示例

```python

def normalize_state(state_name)
# 查询我的数据库并返回state对象的第一个实例
return MyDatabase.State.Search(name=state_name).first()

def text_cleanup(state_name)
return state_name.strip().replace('-', '').lower()

class MyFetcher(BaseFetcherModel)
state = CSSField(
"#state::text",
takes_first=True,
processor=[text_cleanup, normalize_state]
)

fetcher = MyFetcher(url="http://....")
fetcher.parse()

fetcher._raw_data.state
'Sao-Paulo'
fetcher._data.state
<ORM实例 - State - São Paulo>
```

#### 解析方法

任何名为``parse_<field_name>``的方法将在所有选择和解析过程之后运行,它接收该字段的selector或值,具体取决于auto_extract和take_first参数。

示例

```python
def parse_name(self, selector)
return selector.css('::text').extract()[0].upper()
```

在上面的例子中,name字段返回原始selector,在解析方法中,我们可以使用``css``或``xpath``构建额外的查询,并且我们需要从selector中提取()值,并可选地选择第一个元素,并应用我们需要的任何转换。

### 缓存HTML抓取

为了缓存从URL抓取的HTML以供未来的解析和测试,你指定一个缓存模型,默认情况下没有缓存,但你可以使用内置的RedisCache通过

```python
from splinter_model import RedisCache
fetcher = TestFetcher(cache_fetch=True,
cache=RedisCache,
cache_expire=1800)
```

或者指定Redis客户端的参数。

> 这是一个从python ``redis``模块的一般Redis连接

```python
fetcher = TestFetcher(cache_fetch=True,
cache=RedisCache("192.168.0.12:9200"),
cache_expire=1800)
```

你可以创建自己的缓存结构,例如:将HTML缓存到memcached或s3

缓存类只需实现``get``和``set``方法。

```python
from boto import connect_s3

class S3Cache(object)
def __init__(self, *args, **kwargs)
connection = connect_s3(ACCESS_KEY, SECRET_KEY)
self.bucket = connection.get_bucket(BUCKET_ID)

def get(self, key)
value = self.bucket.get_key(key)
return value.get_contents_as_string() if key else None

def set(self, key, value, expire=None)
self.bucket.set_contents(key, value, expire=expire)


fetcher = MyFetcher(url="http://...",
cache_fetch=True,
cache=S3cache,
cache_expire=1800)

```

### 安装

安装简单

如果在运行ubuntu,你可能需要运行

```bash
sudo apt-get install python-scrapy
sudo apt-get install libffi-dev
sudo apt-get install python-dev
```

然后

```bash
pip install splinter_model
```

或者


```bash
git clone https://github.com/rochacbruno/splinter_model
cd splinter_model
pip install -r requirements.txt
python setup.py install
python example.py
```

以下代码用于抓取http://en.m.wikipedia.org/wiki/Guido_van_Rossum的URL

```python
#coding: utf-8

from splinter_model import BaseFetcherModel, CSSField, XPathField


class TestFetcher(BaseFetcherModel)
photo_url = XPathField('//*[@id="content"]/div[1]/table/tr[2]/td/a')

nationality = CSSField(
'#content > div:nth-child(1) > table > tr:nth-child(4) > td > a',
)

links = CSSField(
'#content > div:nth-child(11) > ul > li > a.external::attr(href)',
auto_extract=True
)

def parse_photo_url(self, selector)
return "http://en.m.wikipedia.org/{}".format(
selector.xpath("@href").extract()[0]
)

def parse_nationality(self, selector)
return selector.css("::text").extract()[0]

def parse_name(self, selector)
return selector.extract()[0]

def pre_parse(self, selector=None)
# 在解析之前执行此方法
# 你可以覆盖它,请查看文档字符串

def post_parse(self)
# 在所有解析器之后执行
# 你可以在self._data上加载任何数据
# 访问self._data和self._fields以获取当前数据
# self.selector包含原始页面
self.fetch() 返回原始HTML
self._data.url = self.url


class DummyModel(object)
"""
仅用于测试,它可以是你数据库ORM中的一个模型
"""


if __name__ == "__main__"
from pprint import pprint

fetcher = TestFetcher(cache_fetch=True)
fetcher.url = "http://en.m.wikipedia.org/wiki/Guido_van_Rossum"

映射可以从JSON文件中加载
# fetcher.load_mappings_from_file('path/to/file')
fetcher.mappings['name'] = {
"css": ("#section_0::text")
}

fetcher.parse()

print "Fetcher holds the data"
print fetcher._data.name
print fetcher._data

如何填充一个对象
print "Populating an object"
dummy = DummyModel()

fetcher.populate(dummy, fields=["name", "nationality"])
fields 属性是可选的
print dummy.nationality
pprint(dummy.__dict__)

```

输出


```
Fetcher holds the data
Guido van Rossum
{'links': [u'https://pythonlang.cn/~guido/',
u'http://neopythonic.blogspot.com/',
u'http://www.artima.com/weblogs/index.jsp?blogger=guido',
u'http://python-history.blogspot.com/',
u'https://pythonlang.cn/doc/essays/cp4e.html',
u'http://www.twit.tv/floss11',
u'http://www.computerworld.com.au/index.php/id;66665771',
u'http://www.stanford.edu/class/ee380/Abstracts/081105.html',
u'http://stanford-online.stanford.edu/courses/ee380/081105-ee380-300.asx'],
'name': u'Guido van Rossum',
'nationality': u'Dutch',
'photo_url': 'http://en.m.wikipedia.org//wiki/File:Guido_van_Rossum_OSCON_2006.jpg',
'url': 'http://en.m.wikipedia.org/wiki/Guido_van_Rossum'}
填充对象
Dutch
{'name': u'Guido van Rossum', 'nationality': u'Dutch'}
```

项目详情