安装Scrapy
推荐virtualenv安装,步骤如下:
- 如果没有virtualenv,先安装virtualenv
1
|
sudo pip3 install virtualenv
|
- 创建一个virtualenv环境
1
|
virtualenv --no-site-packages scrapy
|
- 进入目录,激活virtualenv环境
- 安装Scrapy
爬虫基本流程
URL
一切都从一个URL开始。您需要从您想要抓取的站点上获取一些示例url。
1
|
scrapy shell -s USER_AGENT="Mozilla/5.0" http://www.gumtree.com/p/studios-bedsits-rent
|
要在使用scrapy shell时调试问题,请添加--pdb
参数,以便在出现异常时启用交互式调试。
1
|
scrapy shell --pdb https://gumtree.com
|
请求和响应
Scrapy shell为我们做了一些工作,我们给它提供了一个URL,它执行了默认的GET请求并获得了返回码200的响应。这意味着该页面上的信息已经加载并可以使用。如果尝试打印response.body的前50个字符,则会得到以下内容:
1
2
|
>>> response.body[:50]
'<!DOCTYPE html>\n<html>\n<head>\n<meta charset="UTF-8"'
|
物品
下一步是尝试将响应中的数据提取到物品的字段中。因为这个页面的格式是HTML,所以我们使用XPath表达式来实现。
主要字段 |
XPath表达式 |
title |
//*[@itemprop=“name”][1]/text() |
price |
//*[@itemprop=“price”][1]/text() |
description |
//*[@itemprop=“description”][1]/text() |
address |
//*[@itemtype=“http://schema.org/Place"][1]/text() |
image_urls |
//*[@itemprop=“image”][1]/@src |
一个Scrapy项目
首先创建一个名为"properties"的工程:
1
|
$ scrapy startproject properties
|
定义物品
让我们使用文件编辑器打开items.py。其中已经有一些模板代码,但是我们将针对我们的用例修改它。我们将重新定义PropertiesItem类,以添加我们在前一个表中总结的字段。
我们还会添加一些辅助字段后面会用到。需要注意的一件重要的事情是,我们声明一个字段并不意味着我们要在每个爬行器上填充它,或者甚至一起使用它。您可以随意添加任何您认为合适的字段—您可以在以后修改它们。
计算字段 |
Python表达式 |
images |
图像管道将根据image_urls自动填充这个。 |
location |
我们的地理编码管道将在稍后填充此内容。 |
辅助字段 |
Python表达式 |
url |
response.url |
project |
self.settings.get(‘BOT_NAME’) |
spider |
self.name |
server |
socket.gethostname() |
date |
datetime.datetime.now() |
定义完这些字段,我们将items.py修改如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
from scrapy.item import Item, Field
class PropertiesItem(Item):
# Primary fields
title = Field()
price = Field()
description = Field()
address = Field()
image_urls = Field()
# Calculated fields
images = Field()
location = Field()
# Housekeeping fields
url = Field()
project = Field()
spider = Field()
server = Field()
date = Field()
|
编写爬虫
一个爬虫的代码实现了整个$UR^2IM$过程。使用如下命令生成爬虫:
1
2
3
|
$ scrapy genspider basic web
Created spider 'basic' using template 'basic' in module:
properties.spiders.basic
|
使用如下命令查看爬虫模板列表:
basic.py内容如下:
1
2
3
4
5
6
7
8
9
10
11
12
|
import scrapy
class BasicSpider(scrapy.Spider):
name = "basic"
allowed_domains = ["web"]
start_urls = (
'http://www.web/',
)
def parse(self, response):
pass
|
开始编码修改一下爬虫:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
import scrapy
class BasicSpider(scrapy.Spider):
name = "basic"
allowed_domains = ["web"]
start_urls = (
'http://web:9312/properties/property_000000.html',
)
def parse(self, response):
self.log("title: %s" % response.xpath(
'//*[@itemprop="name"][1]/text()').extract())
self.log("price: %s" % response.xpath(
'//*[@itemprop="price"][1]/text()').re('[.0-9]+'))
self.log("description: %s" % response.xpath(
'//*[@itemprop="description"][1]/text()').extract())
self.log("address: %s" % response.xpath(
'//*[@itemtype="http://schema.org/'
'Place"][1]/text()').extract())
self.log("image_urls: %s" % response.xpath(
'//*[@itemprop="image"][1]/@src').extract())
|
使用如下命令运行爬虫:
填充物品
首先我们需要导入PropertiesItem类。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
import scrapy
from properties.items import PropertiesItem
class BasicSpider(scrapy.Spider):
name = "basic"
allowed_domains = ["web"]
start_urls = (
'http://web:9312/properties/property_000000.html',
)
def parse(self, response):
item = PropertiesItem()
item['title'] = response.xpath(
'//*[@itemprop="name"][1]/text()').extract()
item['price'] = response.xpath(
'//*[@itemprop="price"][1]/text()').re('[.0-9]+')
item['description'] = response.xpath(
'//*[@itemprop="description"][1]/text()').extract()
item['address'] = response.xpath(
'//*[@itemtype="http://schema.org/'
'Place"][1]/text()').extract()
item['image_urls'] = response.xpath(
'//*[@itemprop="image"][1]/@src').extract()
return item
|
保存到文件
以下命令可以将爬虫爬取到的数据保存到文件:
1
2
3
4
5
6
|
$ scrapy crawl basic -o items.json
$ scrapy crawl basic -o items.jl
$ scrapy crawl basic -o items.csv
$ scrapy crawl basic -o items.xml
$ scrapy crawl basic -o "ftp://user:[email protected]/items.json "
$ scrapy crawl basic -o "s3://aws_key:aws_secret@scrapybook/items.json"
|
清理
我们首先使用一个很好的实用工具类ItemLoader来替换所有那些看起来很混乱的extract()和xpath()操作。通过使用它,我们的parse()方法改变如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
def parse(self, response):
l = ItemLoader(item=PropertiesItem(), response=response)
l.add_xpath('title', '//*[@itemprop="name"][1]/text()')
l.add_xpath('price', './/*[@itemprop="price"]'
'[1]/text()', re='[,.0-9]+')
l.add_xpath('description', '//*[@itemprop="description"]'
'[1]/text()')
l.add_xpath('address', '//*[@itemtype='
'"http://schema.org/Place"][1]/text()')
l.add_xpath('image_urls', '//*[@itemprop="image"][1]/@src')
return l.load_item()
|
itemloader提供了许多有趣的方式来组合数据、格式化数据和清理数据。其中MapCompose可以用来组合任意一个或多个Python函数来实现复杂的功能。比如:
处理器 |
功能 |
Join() |
将多个结果合并成一个 |
MapCompose(unicode.strip) |
删除开头和结尾的空白字符 |
MapCompose(unicode.strip, unicode.title) |
删除开头和结尾的空白字符,并返回标题结果 |
MapCompose(float) |
转换为数字 |
MapCompose(lambda i: i.replace(’,’, ‘’), float) |
忽略’,‘字符,并转换为数字 |
MapCompose(lambda i: urlparse.urljoin(response.url, i)) |
将相对路径URL转为绝对路径URL |
使用MapCompose修改过的爬虫如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
def parse(self, response):
l.add_xpath('title', '//*[@itemprop="name"][1]/text()',
MapCompose(unicode.strip, unicode.title))
l.add_xpath('price', './/*[@itemprop="price"][1]/text()',
MapCompose(lambda i: i.replace(',', ''), float),
re='[,.0-9]+')
l.add_xpath('description', '//*[@itemprop="description"][1]/text()',
MapCompose(unicode.strip), Join())
l.add_xpath('address',
'//*[@itemtype="http://schema.org/Place"][1]/text()',
MapCompose(unicode.strip))
l.add_xpath('image_urls', '//*[@itemprop="image"][1]/@src',
MapCompose(lambda i: urlparse.urljoin(response.url, i)))
|
创建合约
合约有点像spider的单元测试。它们能让你很快知道是否发生了什么坏了。合约包含在注释中,就在函数名(docstring)后面,并且以@开头。看下面一个例子:
1
2
3
4
5
6
7
8
|
def parse(self, response):
""" This function parses a property page.
@url http://web:9312/properties/property_000000.html
@returns items 1
@scrapes title price description address image_urls
@scrapes url project spider server date
"""
|
使用名scrapy check
检查合约是否满足:
提取更多URL
到目前为止,我们只是爬取一个URL链接。很多网站是有很多分页的。因此一个典型的爬虫在两个方向爬取:
- 水平方向——从一个分页到另一个分页
- 垂直方向——从一个分页到列表页面
将parse函数修改为:
1
2
3
4
5
6
7
8
9
10
|
def parse(self, response):
# Get the next index URLs and yield Requests
next_selector = response.xpath('//*[contains(@class, "next")]//@href')
for url in next_selector.extract():
yield Request(urlparse.urljoin(response.url, url))
# Get item URLs and yield Requests
item_selector = response.xpath('//*[@itemprop="url"]/@href')
for url in item_selector.extract():
yield Request(urlparse.urljoin(response.url, url),
callback=self.parse_item)
|
两个方向爬取使用CrawlSpider
前面的代码太麻烦了,Scrapy使用CrawlSpider可以简单的实现同样的工作。首先使用crawl模板创建爬虫:
1
|
$ scrapy genspider -t crawl easy web
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
class EasySpider(CrawlSpider):
name = 'easy'
allowed_domains = ["web"]
# Start on the first index page
start_urls = (
'http://web:9312/properties/index_00000.html',
)
# Rules for horizontal and vertical crawling
rules = (
Rule(LinkExtractor(restrict_xpaths='//*[contains(@class,"next")]')),
Rule(LinkExtractor(restrict_xpaths='//*[@itemprop="url"]'),
callback='parse_item')
)
|