From:https://piaosanlang.gitbooks.io/spiders/content/
scrapy-cookbook :https://scrapy-cookbook.readthedocs.io/zh_CN/latest/index.html
1. 爬虫框架 Scrapy
爬虫框架中比较好用的是 Scrapy 和 PySpider。
-
PySpider
优点:分布式框架,上手更简单,操作更加简便,因为它增加了 WEB 界面,写爬虫迅速,集成了phantomjs,可以用来抓取js渲染的页面。
缺点:自定义程度低
http://docs.pyspider.org/en/latest/Quickstart/
-
Scrapy
优点:自定义程度高,比 PySpider 更底层一些,适合学习研究,需要学习的相关知识多,拿来研究分布式和多线程等等是最合适不过的。
缺点:非分布式框架(可以用 scrapy-redis 分布式框架)
Scrapy 官方架构图
Scrapy 主要包括了以下组件:
五个功能模块
- 引擎(Scrapy):用来处理整个系统的数据流处理, 数据流的指挥官,负责控制数据流(控制各个模块之间的通信)
- 调度器(Scheduler): 负责引擎发过来的请求URL,压入队列成一个URL的优先队列, 由它来决定下一个要抓取的网址是什么, 同时去除重复的网址
- 下载器(Downloader): 用于下载网页内容, 并将网页内容返回给引擎Scrapy
- 爬虫(Spiders): 爬虫是主要干活的, 用于从特定的网页中提取自己需要的信息, 即所谓的实体(Item)。用户也可以从中提取出链接,让Scrapy继续抓取下一个页面
- 项目管道(item Pipeline): 负责处理爬虫从网页中抽取的实体,主要的功能是持久化实体、验证实体的有效性、清除不需要的信息。当页面被爬虫解析后,将被发送到项目管道,并经过几个特定的次序处理数据。
三大中间件
- 下载器中间件(Downloader Middlewares): 位于Scrapy引擎和下载器之间的框架,主要是处理Scrapy引擎与下载器之间的请求及响应。
- 爬虫中间件(Spider Middlewares): 介于 Scrapy引擎和爬虫之间的框架,主要工作是处理蜘蛛的响应输入和请求输出。
- 调度中间件(Scheduler Middewares): 介于 Scrapy引擎和调度之间的中间件,从Scrapy引擎发送到调度的请求和响应。
Spider参数
Spider 可以通过接受参数来修改其功能。
spider 参数一般用来定义初始URL或者指定限制爬取网站的部分。 您也可以使用其来配置spider的任何功能。
在运行 crawl 时添加 -a 可以传递 Spider 参数:
scrapy crawl myspider -a category=electronics
Spider 在构造器 (constructor) 中获取参数:
import scrapyclass MySpider(Spider):name = 'myspider'def __init__(self, category=None, *args, **kwargs):super(MySpider, self).__init__(*args, **kwargs)self.start_urls = ['http://www.example.com/categories/%s' % category]
脚本运行 Scrapy:https://docs.scrapy.org/en/latest/topics/practices.html
1.1 Scrapy 介绍
Scrapy 是用 Python 开发的一个快速、高层次的 web 抓取框架;
- Scrapy 是一个为了爬取网站数据,提取结构性数据而编写的应用框架。
- 其最初是为了页面抓取 (更确切来说, 网络抓取 )所设计的,也可以应用在获取 API 所返回的数据 (例如 Amazon Associates Web Services ) 或者 通用的网络爬虫。
- Scrapy 用途广泛,可以用于数据挖掘、监测和自动化测试
- Scrapy 使用了 Twisted 异步网络库来处理网络通讯。
整体架构大致如下
Scrapy 运行流程大概如下:
- (1)调度器(Scheduler)从 待下载链接 中取出一个链接(URL)。
- (2)调度器 启动采集模块,即 Spiders模块。
- (3)采集模块 把 URL 传给下载器(Downloader),下载器把资源下载下来。
- (4)提取数据,抽取出目标对象(Item),交给 管道(item pipeline)进行进一步的处理。
- (5)若是解析出的是链接(URL),则把 URL 插入到待爬取队列当中。
Scrapy 安装
文档 官网文档(英文):http://doc.scrapy.org/en/latest/intro/install.html
中文文档(相对官网较老版本):http://scrapy-chs.readthedocs.io/zh_CN/1.0/intro/overview.html
安装 Scrapy:pip install scrapy
验证安装:输入 Scrapy 或者 scrapy(大小写都可以)。如果提示如下命令,就证明安装成功。
Linux Ubuntu 平台
安装 Scrapy
If you prefer to build the python dependencies locally instead of relying on system packages you’ll need to install their required non-python dependencies first:
sudo apt-get install python-dev python-pip libxml2-dev libxslt1-dev zlib1g-dev libffi-dev libssl-dev
You can install Scrapy with pip after that:sudo pip install Scrapy
验证安装:安装完毕之后,输入 scrapy。 注意,这里linux下不要输入Scrapy,linux依然严格区分大小写的
如果出现如下提示,这证明安装成功
1.2 Scrapy 入门教程
大致流程:
- 创建一个 Scrapy 项目
- 定义提取的结构化数据 (Item)
- 编写爬取网站的 spider 并提取出结构化数据 (Item)
- 编写 Item Pipeline 来存储提取到的 Item (即结构化数据)
1.2.1 创建一个 Scrapy 项目
在开始爬取之前必须创建一个新的 Scrapy 项目。 进入打算存储代码的目录中。运行下列命令: scrapy startproject tutorial
该命令将会创建包含下列内容的 tutorial 目录,这些文件分别是:
scrapy.cfg: 项目的配置文件;(用于发布到服务器)
tutorial/: 该项目文件夹。之后将在此编写Python代码。
tutorial/items.py: 项目中的item文件;(定义结构化数据字段field).
tutorial/pipelines.py: 项目中的pipelines文件;(用于存放执行后期数据处理的功能,定义如何存储结构化数据)
tutorial/settings.py: 项目的设置文件;(如何修改User-Agent,设置爬取时间间隔,设置代理,配置中间件等等)
tutorial/spiders/: 放置spider代码的目录;(编写爬取网站规则)
windows 下创建:
Pycharm 打开 Scrapy 工程:
1.2.2 定义 Item
Item 定义结构化数据字段,用来保存爬取到的数据;其使用方法和python字典类似。可以通过创建一个 scrapy.Item 类, 并且定义类型为 scrapy.Field 的类属性来定义一个 Item。首先根据需要从腾讯招聘获取到的数据对item进行建模。 我们需要从腾讯招聘中获取 职位名称、职位详情页url、职位类别、人数、工作地点以及发布时间。 对此,在item中定义相应的字段。编辑 tutorial 目录中的 items.py 文件:
import scrapyclass RecruitItem(scrapy.Item):name = scrapy.Field()detailLink = scrapy.Field()catalog = scrapy.Field()recruitNumber = scrapy.Field()workLocation = scrapy.Field()publishTime = scrapy.Field()
1.2.3 编写第一个爬虫 (Spider)
创建一个 Spider,必须继承 'scrapy.Spider' 类, 需要定义以下三个属性:
- name: spider 名字;必须是唯一的
- start_urls: 初始的 URL 列表
- parse(self, response):每个初始 URL 完成下载后被调用。这个函数要完成的功能:
1. 负责解析返回的网页数据(response.body),提取结构化数据(生成item)
2. 生成需要下一页的请求 URL。
以下为我们的第一个 Spider 代码,保存在 tutorial/spiders 目录下的 tencent_spider.py 文件中:
import scrapyclass RecruitSpider(scrapy.spiders.Spider):name = "tencent"allowed_domains = ["hr.tencent.com"]start_urls = ["http://hr.tencent.com/position.php?&start=0#a"]def parse(self, response):f = open('tengxun.txt', 'wb')f.write(response.body)f.close()
爬取
进入项目的根目录,执行下列命令启动 spider:
scrapy crawl tencent
crawl tencent 启动用于爬取 tencent 的 spider,您将得到类似的输出:
现在,查看当前目录,会注意到有文件被创建了: tengxun.txt,正如我们的 parse 方法里做的一样。
注意,在刚启动的时候会有一段 error 信息,不用理会(以后会说明,可现在自行查找结果)
2016-08-11 13:07:35 [boto] ERROR: Caught exception reading instance data
Traceback (most recent call last):File "/usr/lib/python2.7/dist-packages/boto/utils.py", line 210, in retry_urlr = opener.open(req, timeout=timeout)File "/usr/lib/python2.7/urllib2.py", line 429, in openresponse = self._open(req, data)File "/usr/lib/python2.7/urllib2.py", line 447, in _open'_open', req)File "/usr/lib/python2.7/urllib2.py", line 407, in _call_chainresult = func(*args)File "/usr/lib/python2.7/urllib2.py", line 1228, in http_openreturn self.do_open(httplib.HTTPConnection, req)File "/usr/lib/python2.7/urllib2.py", line 1198, in do_openraise URLError(err)
URLError: <urlopen error timed out>
刚才发生了什么?
- Scrapy 为 Spider 的 start_urls 属性中的每个 URL 创建了 scrapy.Request 对象,并将 parse 方法作为回调函数(callback)赋值给了 Request。
- Request 对象经过调度,执行生成 scrapy.http.Response 对象并送回给 parse() 方法。
提取 Item
Scrapy 内置的 Selectors 模块提供了对 XPath 和 CSS Selector 的支持。也可以单独拿出来使用
单独使用 示例:
from scrapy import Selectortemp_string = '''
<bookstore><book><title lang="eng">Harry Potter</title><price>29.99</price></book><book><title lang="eng">Learning XML</title><price>39.95</price></book>
</bookstore>
'''if __name__ == '__main__':s = Selector(text=temp_string)print(s.xpath('//book[1]/title/text()').extract_first())print(s.xpath('//book[1]/price/text()').extract_first())pass
XPath 表达式的例子及对应的含义:
/html/head/title 选择<HTML>文档中 <head> 标签内的 <title> 元素
/html/head/title/text() 选择上面提到的 <title> 元素的文字
//td 选择所有的 <td> 元素
//div[@class="mine"] 选择所有具有 class="mine" 属性的 div 元素
Selector 有四个基本的方法:
xpath() 传入xpath表达式,返回该表达式所对应的所有节点的selector list列表 。
css() 传入CSS表达式,返回该表达式所对应的所有节点的selector list列表.
extract() 序列化该节点为unicode字符串并返回list。
re() 根据传入的正则表达式对数据进行提取,返回unicode字符串list列表。
尝试 Selector 选择器
为了介绍 Selector的使用方法,接下来我们将要使用内置的 scrapy shell 。Scrapy Shell 需要您预装好 IPython (一个扩展的Python终端)。您需要进入项目的根目录,执行下列命令来启动 shell:
scrapy shell "http://hr.tencent.com/position.php?&start=0#a"
注解: 当您在终端运行 Scrapy 时,请一定记得给 url 地址加上引号,否则包含参数的 url (例如 & 字符)会导致 Scrapy 运行失败。
shell 的输出类似:
当 shell 载入后,将得到一个包含 response 数据的本地 response 变量。
输入 response.body 将输出 response 的包体, 输出 response.headers 可以看到 response 的包头。
当输入 response.selector 时, 将获取到一个response 初始化的类 Selector 的对象。
此时,可以通过使用 response.selector.xpath() 或 response.selector.css() 来对 response 进行查询。
scrapy 对 response.selector.xpath() 及 response.selector.css() 提供了一些快捷方式,例如 response.xpath() 或 response.css()
response.xpath('//title')
[<Selector xpath='//title' data=u'<title>\u804c\u4f4d\u641c\u7d22 | \u793e\u4f1a\u62db\u8058 | Tencent \u817e\u8baf\u62db\u8058</title'>]response.xpath('//title').extract()
[u'<title>\u804c\u4f4d\u641c\u7d22 | \u793e\u4f1a\u62db\u8058 | Tencent \u817e\u8baf\u62db\u8058</title>']print response.xpath('//title').extract()[0]
<title>职位搜索 | 社会招聘 | Tencent 腾讯招聘</title>response.xpath('//title/text()')
<Selector xpath='//title/text()' data=u'\u804c\u4f4d\u641c\u7d22 | \u793e\u4f1a\u62db\u8058 | Tencent \u817e\u8baf\u62db\u8058'>response.xpath('//title/text()')[0].extract()
u'\u804c\u4f4d\u641c\u7d22 | \u793e\u4f1a\u62db\u8058 | Tencent \u817e\u8baf\u62db\u8058'print response.xpath('//title/text()')[0].extract()
职位搜索 | 社会招聘 | Tencent 腾讯招聘response.xpath('//title/text()').re('(\w+):')
[u'\u804c\u4f4d\u641c\u7d22',u'\u793e\u4f1a\u62db\u8058',u'Tencent',u'\u817e\u8baf\u62db\u8058']
提取数据
现在,从页面中提取些有用的数据。
# 通过 XPath 选择该页面中网站列表里所有 lass=even 元素
site = response.xpath('//*[@class="even"]')# 职位名称:
print site[0].xpath('./td[1]/a/text()').extract()[0]
# TEG15-运营开发工程师(深圳)# 职位名称详情页:
print site[0].xpath('./td[1]/a/@href').extract()[0]
position_detail.php?id=20744&keywords=&tid=0&lid=0# 职位类别:
print site[0].xpath('./td[2]/text()').extract()[0]
# 技术类
对于 .xpath() 调用返回 selector 组成的 list, 因此可以拼接更多的 .xpath() 来进一步获取某个节点。
for sel in response.xpath('//*[@class="even"]'):name = sel.xpath('./td[1]/a/text()').extract()[0]detailLink = sel.xpath('./td[1]/a/@href').extract()[0]catalog = sel.xpath('./td[2]/text()').extract()[0]recruitNumber = sel.xpath('./td[3]/text()').extract()[0]workLocation = sel.xpath('./td[4]/text()').extract()[0]publishTime = sel.xpath('./td[5]/text()').extract()[0]print name, detailLink, catalog,recruitNumber,workLocation,publishTime
在我们的 tencent_spider.py 文件修改成如下代码:
import scrapyclass RecruitSpider(scrapy.spiders.Spider):name = "tencent"allowed_domains = ["hr.tencent.com"]start_urls = ["http://hr.tencent.com/position.php?&start=0#a"]def parse(self, response):for sel in response.xpath('//*[@class="even"]'):name = sel.xpath('./td[1]/a/text()').extract()[0]detailLink = sel.xpath('./td[1]/a/@href').extract()[0]catalog = sel.xpath('./td[2]/text()').extract()[0]recruitNumber = sel.xpath('./td[3]/text()').extract()[0]workLocation = sel.xpath('./td[4]/text()').extract()[0]publishTime = sel.xpath('./td[5]/text()').extract()[0]print name, detailLink, catalog,recruitNumber,workLocation,publishTime
如图所示:
现在尝试再次爬取 hr.tencent.com,您将看到爬取到的网站信息被成功输出:
scrapy crawl tencent
运行过程:
使用 item
Item 对象是自定义的 python 字典。可以使用标准的字典语法来获取到其每个字段的值。输入 `scrapy shell'
import scrapyclass RecruitItem(scrapy.Item):name = scrapy.Field()detailLink = scrapy.Field()catalog = scrapy.Field()recruitNumber = scrapy.Field()workLocation = scrapy.Field()publishTime = scrapy.Field()item = RecruitItem()
item['name'] = 'sanlang'
item['name']
'sanlang'
一般来说,Spider 将会将爬取到的数据以 Item 对象返回。所以为了将爬取的数据返回,最终 tencent_spider.py 代码将是:
import scrapy
from tutorial.items import RecruitItem
class RecruitSpider(scrapy.spiders.Spider):name = "tencent"allowed_domains = ["hr.tencent.com"]start_urls = ["http://hr.tencent.com/position.php?&start=0#a"]def parse(self, response):for sel in response.xpath('//*[@class="even"]'):name = sel.xpath('./td[1]/a/text()').extract()[0]detailLink = sel.xpath('./td[1]/a/@href').extract()[0]catalog = sel.xpath('./td[2]/text()').extract()[0]recruitNumber = sel.xpath('./td[3]/text()').extract()[0]workLocation = sel.xpath('./td[4]/text()').extract()[0]publishTime = sel.xpath('./td[5]/text()').extract()[0]print name, detailLink, catalog,recruitNumber,workLocation,publishTimeitem = RecruitItem()item['name']=name.encode('utf-8')item['detailLink']=detailLink.encode('utf-8')item['catalog']=catalog.encode('utf-8')item['recruitNumber']=recruitNumber.encode('utf-8')item['workLocation']=workLocation.encode('utf-8')item['publishTime']=publishTime.encode('utf-8')yield item
现在对 hr.tencent.com 进行爬取将会产生 RecruitItem 对象:
保存爬取到的数据
最简单存储爬取的数据的方式是使用 Feed exports:
scrapy crawl tencent -o items.json
该命令将采用 JSON 格式对爬取的数据进行序列化,生成 items.json 文件。
如果需要对爬取到的item做更多更为复杂的操作,您可以编写 Item Pipeline 。 类似于我们在创建项目时对Item做的,用于您编写自己的 tutorial/pipelines.py 也被创建。 不过如果您仅仅想要保存item,您不需要实现任何的pipeline。
1.2.4 Item Pipelines
当 Item 在 Spider 中被收集之后,它将会被传递到 Item Pipeline。
每个 Item Pipeline 组件接收到 Item,定义一些操作行为,比如决定此 Item 是丢弃而存储。
item pipeline 的一些典型应用:
- 验证爬取的数据 (检查 item 包含某些字段,比如说 name 字段)。
- 查重 (并丢弃)。
- 将爬取结果保存到文件或者数据库中。
编写 item pipeline
编写 item pipeline 很简单,item pipiline 组件是一个独立的 Python 类,必须实现 process_item 方法:
- process_item(self, item, spider):当 Item 在 Spider 中被收集之后,都需要调用该方法。参数: item - 爬取的结构化数据。 spider – 爬取该 item 的 spider
- open_spider(self, spider):当 spider 被开启时,这个方法被调用。参数:spider – 被开启的spider
- close_spider(spider):当 spider 被关闭时,这个方法被调用。参数:spider – 被关闭的spider
将 item 写入 JSON 文件
以下 pipeline 将所有爬取到的 item,存储到一个独立地 items.json 文件,每行包含一个序列化为 'JSON' 格式的 'item':
import jsonclass JsonWriterPipeline(object):def __init__(self):self.file = open('items.json', 'wb')def process_item(self, item, spider):line = json.dumps(dict(item),ensure_ascii=False) + "\n"self.file.write(line)return item
启用一个Item Pipeline组件
为了启用 Item Pipeline 组件,必须将它的类添加到 settings.py 文件 ITEM_PIPELINES 配置,就像下面这个例子:
ITEM_PIPELINES = {#'tutorial.pipelines.PricePipeline': 300,'tutorial.pipelines.JsonWriterPipeline': 800,
}
分配给每个类的整型值,确定了他们运行的顺序,item按数字从低到高的顺序,通过 pipeline,通常将这些数字定义在0-1000范围内。
在这里优化:
以下 pipeline 将所有爬取到的 item,存储到一个独立地 items.json 文件,每行包含一个序列化为 'JSON' 格式的 'item':
import json
import codecsclass JsonWriterPipeline(object):def __init__(self):self.file = codecs.open('items.json', 'w', encoding='utf-8')def process_item(self, item, spider):line = json.dumps(dict(item), ensure_ascii=False) + "\n"self.file.write(line)return itemdef spider_closed(self, spider):self.file.close()
针对 spider 里面的 utf-8 编码格式去掉 .encode('utf-8')
item = RecruitItem()
item['name']=name.encode('utf-8')
item['detailLink']=detailLink.encode('utf-8')
item['catalog']=catalog.encode('utf-8')
item['recruitNumber']=recruitNumber.encode('utf-8')
item['workLocation']=workLocation.encode('utf-8')
item['publishTime']=publishTime.encode('utf-8')
将 item 写入 MongoDB
- from_crawler(cls, crawler):如果使用,这类方法被调用创建爬虫管道实例。必须返回管道的一个新实例。crawler提供存取所有Scrapy核心组件配置和信号管理器; 对于pipelines这是一种访问配置和信号管理器 的方式。参数: crawler (Crawler object) – crawler that uses this pipeline
例子中,我们将使用 pymongo 将 Item 写到 MongoDB。MongoDB 的地址和数据库名称在 Scrapy setttings.py 配置文件中;这个例子主要是说明如何使用 from_crawler() 方法
import pymongoclass MongoPipeline(object):collection_name = 'scrapy_items'def __init__(self, mongo_uri, mongo_db):self.mongo_uri = mongo_uriself.mongo_db = mongo_db@classmethoddef from_crawler(cls, crawler):return cls(mongo_uri=crawler.settings.get('MONGO_URI'),mongo_db=crawler.settings.get('MONGO_DATABASE', 'items'))def open_spider(self, spider):self.client = pymongo.MongoClient(self.mongo_uri)self.db = self.client[self.mongo_db]def close_spider(self, spider):self.client.close()def process_item(self, item, spider):self.db[self.collection_name].insert(dict(item))return item
1.3 Spider 类
Spider类 定义了如何爬取某个(或某些)网站。包括了爬取的动作(例如:是否跟进链接) 以及如何从网页的内容中提取结构化数据(爬取item)。 换句话说,Spider 就是定义爬取的动作及分析某个网页(或者是有些网页)的地方。
class scrapy.spider.Spider
scrapy 为我们提供了5种 spide r用于构造请求,解析数据、返回 item。常用的就 scrapy.spider、scrapy.crawlspider两种。
Spider 是最简单的 spider。每个 spider 必须继承自该类。Spider 并没有提供什么特殊的功能。其仅仅请求给定的 start_urls / start_requests,并根据返回的结果调用 spider 的 parse 方法。
- name:定义 spider 名字的字符串。例如,如果spider爬取 mywebsite.com ,该 spider 通常会被命名为 mywebsite
- allowed_domains:可选。包含了spider允许爬取的域名(domain)列表(list)
- start_urls:初始 URL 列表。当没有制定特定的 URL 时,spider 将从该列表中开始进行爬取。
- start_requests():当 spider 启动爬取并且未指定 start_urls 时,该方法被调用。如果您想要修改最初爬取某个网站。
- parse(self, response):当请求 url 返回网页没有指定回调函数时,默认下载回调方法。参数:response (Response) – 返回网页信息的 response
- log(message[, level, component]):使用 scrapy.log.msg() 方法记录(log)message。 更多数据请参见 Logging
下面是 spider 常用到的 属性 和 方法(scrapy一览及源码解析:https://www.cnblogs.com/pontoon/p/10247589.html)
属性、方法 | 功能 | 简述 |
name | 爬虫的名称 | 启动爬虫的时候会用到 |
start_urls | 起始 url | 是一个列表,默认被 start_requests 调用 |
allowd_doamins | 对 url 进行的简单过滤 | 当请求 url 没有被 allowd_doamins 匹配到时,会报一个非常恶心的错, |
start_requests() | 第一次请求 | 自己的 spider 可以重写,突破一些简易的反爬机制 |
custom_settings | 定制 settings | 可以对每个爬虫定制 settings 配置 |
from_crawler | 实例化入口 | 在 scrapy 的各个组件的源码中,首先执行的就是它 |
关于 spider 我们可以定制 start_requests、可以单独的设置 custom_settings、也可以设置请
例如,如果您需要在启动时以 POST 登录某个网站,你可以这么写:
class MySpider(scrapy.Spider):name = 'myspider'def start_requests(self):return [scrapy.FormRequest("http://www.example.com/login",formdata={'user': 'john', 'pass': 'secret'},callback=self.logged_in)]def logged_in(self, response):# here you would extract links to follow and return Requests for# each of them, with another callbackpass
Spider 示例
让我们来看一个例子:
import scrapyclass MySpider(scrapy.Spider):name = 'example.com'allowed_domains = ['example.com']start_urls = ['http://www.example.com/1.html','http://www.example.com/2.html','http://www.example.com/3.html',]def parse(self, response):self.log('A response from %s just arrived!' % response.url)
另一个在单个回调函数中返回多个 Request 以及 Item 的例子:
import scrapy
from myproject.items import MyItemclass MySpider(scrapy.Spider):name = 'example.com'allowed_domains = ['example.com']start_urls = ['http://www.example.com/1.html','http://www.example.com/2.html','http://www.example.com/3.html',]def parse(self, response):sel = scrapy.Selector(response)for h3 in response.xpath('//h3').extract():yield MyItem(title=h3)for url in response.xpath('//a/@href').extract():yield scrapy.Request(url, callback=self.parse)
案例:腾讯招聘网翻页功能
import scrapy
from tutorial.items import RecruitItem
import re
class RecruitSpider(scrapy.Spider):name = "tencent"allowed_domains = ["hr.tencent.com"]start_urls = ["http://hr.tencent.com/position.php?&start=0#a"]def parse(self, response):for sel in response.xpath('//*[@class="even"]'):name = sel.xpath('./td[1]/a/text()').extract()[0]detailLink = sel.xpath('./td[1]/a/@href').extract()[0]catalog =Noneif sel.xpath('./td[2]/text()'):catalog = sel.xpath('./td[2]/text()').extract()[0]recruitNumber = sel.xpath('./td[3]/text()').extract()[0]workLocation = sel.xpath('./td[4]/text()').extract()[0]publishTime = sel.xpath('./td[5]/text()').extract()[0]#print name, detailLink, catalog,recruitNumber,workLocation,publishTimeitem = RecruitItem()item['name']=name.encode('utf-8')item['detailLink']=detailLink.encode('utf-8')if catalog:item['catalog']=catalog.encode('utf-8')item['recruitNumber']=recruitNumber.encode('utf-8')item['workLocation']=workLocation.encode('utf-8')item['publishTime']=publishTime.encode('utf-8')yield itemnextFlag = response.xpath('//*[@id="next"]/@href')[0].extract()if 'start' in nextFlag:curpage = re.search('(\d+)',response.url).group(1)page =int(curpage)+10url = re.sub('\d+',str(page),response.url)print urlyield scrapy.Request(url, callback=self.parse)
执行:scrapy crawl tencent -L INFO
1.4 CrawlSpider 类
scrapy.spiders.CrawlSpider
CrawlSpider 定义了一些规则(rule)来提供跟进 link 的方便的机制。除了从 Spider 继承过来的(您必须提供的)属性外(name、allow_domains),其提供了一个新的属性:
- rules:包含一个(或多个) 规则对象的集合(list)。 每个Rule对爬取网站的动作定义了特定操作。 如果多个 rule 匹配了相同的链接,则根据规则在本集合中被定义的顺序,第一个会被使用。
- parse_start_url(response):当 start_url 的请求返回时,该方法被调用
爬取规则(Crawling rules)
class scrapy.contrib.spiders.Rule(link_extractor, callback=None, cb_kwargs=None, follow=None, process_links=None, process_request=None)
- link_extractor:其定义了如何从爬取到的页面中提取链接。
- callback:指定 spider 中哪个函数将会被调用。 从 link_extractor 中每获取到链接时将会调用该函数。该回调函数接受一个response 作为其第一个参数。注意:当编写爬虫规则时,请避免使用 parse作为回调函数。由于 CrawlSpider 使用 parse 方法来实现其逻辑,如果您覆盖了 parse方法,crawl spider将会运行失败。
- cb_kwargs:包含传递给回调函数的参数 (keyword argument) 的字典。
- follow:是一个布尔(boolean)值,指定了根据该规则从response提取的链接是否需要跟进。 如果callback为None,follow默认设置为True ,否则默认为False。
- process_links:指定该spider中哪个的函数将会被调用,从link_extractor中获取到链接列表时将会调用该函数。该方法常用于过滤参数
- process_request:指定该spider中哪个的函数将会被调用,该规则提取到每个request时都会调用该函数 (用来过滤request)
CrawlSpider 案例
还是以腾讯招聘为例,给出配合 rule 使用 CrawlSpider 的例子:
首先运行
scrapy shell "http://hr.tencent.com/position.php?&start=0#a"
导入匹配规则:
from scrapy.linkextractors import LinkExtractor
page_lx = LinkExtractor(allow=('position.php?&start=\d+'))
查询匹配结果:
page_lx.extract_links(response)
没有查到:
page_lx = LinkExtractor(allow=(r'position\.php\?&start=\d+'))
page_lx.extract_links(response)[Link(url='http://hr.tencent.com/position.php?start=10', text='2', fragment='', nofollow=False),
Link(url='http://hr.tencent.com/position.php?start=20', text='3', fragment='', nofollow=False),
Link(url='http://hr.tencent.com/position.php?start=30', text='4', fragment='', nofollow=False),
Link(url='http://hr.tencent.com/position.php?start=40', text='5', fragment='', nofollow=False),
Link(url='http://hr.tencent.com/position.php?start=50', text='6', fragment='', nofollow=False),
Link(url='http://hr.tencent.com/position.php?start=60', text='7', fragment='', nofollow=False),
Link(url='http://hr.tencent.com/position.php?start=70', text='...', fragment='', nofollow=False),
Link(url='http://hr.tencent.com/position.php?start=1300', text='131', fragment='', nofollow=False)]
len(page_lx.extract_links(response))
那么,scrapy shell 测试完成之后,修改以下代码
#提取匹配 'http://hr.tencent.com/position.php?&start=\d+'的链接
page_lx = LinkExtractor(allow=('start=\d+'))rules = [
#提取匹配,并使用spider的parse方法进行分析;并跟进链接(没有callback意味着follow默认为True)
Rule(page_lx, callback='parse',follow=True)
]
这么写对吗? callback 千万不能写 parse,一定运行有错误!!保存以下代码为 tencent_crawl.py
# -*- coding:utf-8 -*-
import scrapy
from tutorial.items import RecruitItem
from scrapy.spiders import CrawlSpider, Rule
from scrapy.linkextractors import LinkExtractorclass RecruitSpider(CrawlSpider):name = "tencent_crawl"allowed_domains = ["hr.tencent.com"]start_urls = ["http://hr.tencent.com/position.php?&start=0#a"]#提取匹配 'http://hr.tencent.com/position.php?&start=\d+'的链接page_lx = LinkExtractor(allow=('start=\d+'))rules = [#提取匹配,并使用spider的parse方法进行分析;并跟进链接(没有callback意味着follow默认为True)Rule(page_lx, callback='parseContent',follow=True)]def parseContent(self, response):print response.urlfor sel in response.xpath('//*[@class="even"]'):name = sel.xpath('./td[1]/a/text()').extract()[0]detailLink = sel.xpath('./td[1]/a/@href').extract()[0]catalog =Noneif sel.xpath('./td[2]/text()'):catalog = sel.xpath('./td[2]/text()').extract()[0]recruitNumber = sel.xpath('./td[3]/text()').extract()[0]workLocation = sel.xpath('./td[4]/text()').extract()[0]publishTime = sel.xpath('./td[5]/text()').extract()[0]#print name, detailLink, catalog,recruitNumber,workLocation,publishTimeitem = RecruitItem()item['name']=name.encode('utf-8')item['detailLink']=detailLink.encode('utf-8')if catalog:item['catalog']=catalog.encode('utf-8')item['recruitNumber']=recruitNumber.encode('utf-8')item['workLocation']=workLocation.encode('utf-8')item['publishTime']=publishTime.encode('utf-8')yield item
可以修改配置文件settings.py,添加 LOG_LEVEL='INFO'
运行: scrapy crawl tencent_crawl
process_links 参数:动态网页爬取,动态 url 的处理
在爬取 https://bitsharestalk.org 的时候,发现网站会为每一个 url 增加一个 sessionid 属性,可能是为了标记用户访问历史,而且这个 seesionid 随着每次访问都会动态变化,这就为爬虫的去重处理(即标记已经爬取过的网站)和提取规则增加了难度。
比如:https://bitsharestalk.org/index.php?board=5.0 会变成 https://bitsharestalk.org/index.phpPHPSESSID=9771d42640ab3c89eb77e8bd9e220b53&board=5.0
下面介绍几种处理方法
仅适用你的爬虫使用的是 scrapy.contrib.spiders.CrawlSpider,在这个内置爬虫中,你提取 url 要通过 Rule类来进行提取,其自带了对提取后的 url 进行加工的函数。
rules = (Rule(LinkExtractor(allow=(r"https://bitsharestalk\.org/index\.php\?PHPSESSID\S*board=\d+\.\d+$",r"https://bitsharestalk\.org/index\.php\?board=\d+\.\d+$")),process_links='link_filtering' # 默认函数process_links), Rule(LinkExtractor(allow=(r" https://bitsharestalk\.org/index\.php\?PHPSESSID\S*topic=\d+\.\d+$",r"https://bitsharestalk\.org/index\.php\?topic=\d+\.\d+$",), ),callback="extractPost",follow=True, process_links='link_filtering'),Rule(LinkExtractor(allow=(r"https://bitsharestalk\.org/index\.php\?PHPSESSID\S*action=profile;u=\d+$",r"https://bitsharestalk\.org/index\.php\?action=profile;u=\d+$",), ),callback="extractUser", process_links='link_filtering'))def link_filtering(self, links):ret = []for link in links:url = link.url# print "This is the yuanlai ", link.urlurlfirst, urllast = url.split(" ? ")if urllast:link.url = urlfirst + " ? " + urllast.split(" & ", 1)[1]# print link.urlreturn links
link_filtering() 函数对 url 进行了处理,过滤掉了 sessid,关于 Rule类的 process_links 函数和 links 类,官方文档中并没有给出介绍,给出一个参考 https://groups.google.com/forum/#!topic/scrapy-users/RHGtm_2GO1M(也许需要梯子,你懂得)
如果你是自己实现的爬虫,那么 url 的处理更是可定制的,只需要自己处理一下就可以了。
process_request 参数:修改请求参数
import re
from scrapy.spiders import CrawlSpider, Rule
from scrapy.linkextractors import LinkExtractorclass WeiboSpider(CrawlSpider):name = 'weibo'allowed_domains = ['weibo.com']# 不加www,则匹配不到 cookie, get_login_cookie()方法正则代完善start_urls = ['http://www.weibo.com/u/1876296184']rules = (Rule(# 微博个人页面的规则,或/u/或/n/后面跟一串数字LinkExtractor(allow=r'^http:\/\/(www\.)?weibo.com/[a-z]/.*'),process_request='process_request',callback='parse_item', follow=True),)cookies = Nonedef process_request(self, request):link = request.urlpage = re.search(r'page=\d*', link).group()tp = re.search(r'type=\d+', link).group()new_request = request.replace(cookies=self.cookies, url='.../questionType?' + page + "&" + tp)return new_request
1.5 Logging
Scrapy 提供了 log 功能。您可以通过 logging 模块使用。
Log levels
Scrapy 提供5层 logging 级别:
- CRITICAL --- 严重错误(critical)
- ERROR --- 一般错误(regular errors)
- WARNING --- 警告信息(warning messages)
- INFO --- 一般信息(informational messages)
- DEBUG --- 调试信息(debugging messages)
默认情况下 python 的 logging 模块将日志打印到了标准输出中,且只显示了大于等于 WARNING 级别的日志,这说明默认的日志级别设置为 WARNING(日志级别等级CRITICAL > ERROR > WARNING > INFO > DEBUG,默认的日志格式为DEBUG级别
如何设置 log 级别
您可以通过终端选项(command line option) --loglevel/-L 或 LOG_LEVEL 来设置log级别。
-
scrapy crawl tencent_crawl -L INFO
-
可以修改配置文件 settings.py,添加
LOG_LEVEL='INFO'
scrapy crawl tencent_crawl -L INFO
也可以修改配置文件settings.py,添加 LOG_LEVEL='INFO'
在 Spider 中添加 log
Scrapy 为每个 Spider 实例记录器提供了一个 logger,可以这样访问:
import scrapyclass MySpider(scrapy.Spider):name = 'myspider'start_urls = ['http://scrapinghub.com']def parse(self, response):self.logger.info('Parse function called on %s', response.url)
logger 是用 Spider 的名称创建的,但是你可以用你想要的任何自定义 logging。例如:
import logging
import scrapylogger = logging.getLogger('zhangsan')class MySpider(scrapy.Spider):name = 'myspider'start_urls = ['http://scrapinghub.com']def parse(self, response):logger.info('Parse function called on %s', response.url)
Logging 设置
以下设置可以被用来配置logging:
LOG_ENABLED 默认: True,启用logging
LOG_ENCODING 默认: 'utf-8',logging使用的编码
LOG_FILE 默认: None,logging输出的文件名
LOG_LEVEL 默认: 'DEBUG',log的最低级别
LOG_STDOUT 默认: False。如果为 True,进程所有的标准输出(及错误)将会被重定向到log中。例如,执行 print 'hello' ,其将会在Scrapy log中显示。
案例 (一) ( self.logger )
tencent_crawl.py 添加日志信息如下:
'''添加日志信息'''print 'print',response.urlself.logger.info('info on %s', response.url)self.logger.warning('WARNING on %s', response.url)self.logger.debug('info on %s', response.url)self.logger.error('info on %s', response.url)
完整版如下:
# -*- coding:utf-8 -*-
import scrapy
from tutorial.items import RecruitItem
from scrapy.spiders import CrawlSpider, Rule
from scrapy.linkextractors import LinkExtractorclass RecruitSpider(CrawlSpider):name = "tencent_crawl"allowed_domains = ["hr.tencent.com"]start_urls = ["http://hr.tencent.com/position.php?&start=0#a"]#提取匹配 'http://hr.tencent.com/position.php?&start=\d+'的链接page_lx = LinkExtractor(allow=('start=\d+'))rules = [#提取匹配,并使用spider的parse方法进行分析;并跟进链接(没有callback意味着follow默认为True)Rule(page_lx, callback='parseContent',follow=True)]def parseContent(self, response):#print("print settings: %s" % self.settings['LOG_FILE'])'''添加日志信息'''print 'print',response.urlself.logger.info('info on %s', response.url)self.logger.warning('WARNING on %s', response.url)self.logger.debug('info on %s', response.url)self.logger.error('info on %s', response.url)for sel in response.xpath('//*[@class="even"]'):name = sel.xpath('./td[1]/a/text()').extract()[0]detailLink = sel.xpath('./td[1]/a/@href').extract()[0]catalog =Noneif sel.xpath('./td[2]/text()'):catalog = sel.xpath('./td[2]/text()').extract()[0]recruitNumber = sel.xpath('./td[3]/text()').extract()[0]workLocation = sel.xpath('./td[4]/text()').extract()[0]publishTime = sel.xpath('./td[5]/text()').extract()[0]#print name, detailLink, catalog,recruitNumber,workLocation,publishTimeitem = RecruitItem()item['name']=nameitem['detailLink']=detailLinkif catalog:item['catalog']=catalogitem['recruitNumber']=recruitNumberitem['workLocation']=workLocationitem['publishTime']=publishTimeyield item
在 settings 文件中,修改添加信息
LOG_FILE='ten.log'
LOG_LEVEL='INFO'
接下来执行:scrapy crawl tencent_crawl。或者 command line 命令行执行:
scrapy crawl tencent_crawl --logfile 'ten.log' -L INFO
输出如下
print http://hr.tencent.com/position.php?start=10
print http://hr.tencent.com/position.php?start=1340
print http://hr.tencent.com/position.php?start=0
print http://hr.tencent.com/position.php?start=1320
print http://hr.tencent.com/position.php?start=1310
print http://hr.tencent.com/position.php?start=1300
print http://hr.tencent.com/position.php?start=1290
print http://hr.tencent.com/position.php?start=1260
ten.log 文件中记录,可以看到级别大于 INFO 日志输出
2016-08-15 23:10:57 [tencent_crawl] INFO: info on http://hr.tencent.com/position.php?start=70
2016-08-15 23:10:57 [tencent_crawl] WARNING: WARNING on http://hr.tencent.com/position.php?start=70
2016-08-15 23:10:57 [tencent_crawl] ERROR: info on http://hr.tencent.com/position.php?start=70
2016-08-15 23:10:57 [tencent_crawl] INFO: info on http://hr.tencent.com/position.php?start=1320
2016-08-15 23:10:57 [tencent_crawl] WARNING: WARNING on http://hr.tencent.com/position.php?start=1320
2016-08-15 23:10:57 [tencent_crawl] ERROR: info on http://hr.tencent.com/position.php?start=1320
案例(二)( logging.getLogger )
tencent_spider.py 添加日志信息如下:logger = logging.getLogger('zhangsan')
'''添加日志信息'''print 'print',response.urlself.logger.info('info on %s', response.url)self.logger.warning('WARNING on %s', response.url)self.logger.debug('info on %s', response.url)self.logger.error('info on %s', response.url)
完整版如下:
import scrapy
from tutorial.items import RecruitItem
import re
import logginglogger = logging.getLogger('zhangsan')class RecruitSpider(scrapy.spiders.Spider):name = "tencent"allowed_domains = ["hr.tencent.com"]start_urls = ["http://hr.tencent.com/position.php?&start=0#a"]def parse(self, response):#logger.info('spider tencent Parse function called on %s', response.url)'''添加日志信息'''print 'print',response.urllogger.info('info on %s', response.url)logger.warning('WARNING on %s', response.url)logger.debug('info on %s', response.url)logger.error('info on %s', response.url)for sel in response.xpath('//*[@class="even"]'):name = sel.xpath('./td[1]/a/text()').extract()[0]detailLink = sel.xpath('./td[1]/a/@href').extract()[0]catalog =Noneif sel.xpath('./td[2]/text()'):catalog = sel.xpath('./td[2]/text()').extract()[0]recruitNumber = sel.xpath('./td[3]/text()').extract()[0]workLocation = sel.xpath('./td[4]/text()').extract()[0]publishTime = sel.xpath('./td[5]/text()').extract()[0]#print name, detailLink, catalog,recruitNumber,workLocation,publishTimeitem = RecruitItem()item['name']=nameitem['detailLink']=detailLinkif catalog:item['catalog']=catalogitem['recruitNumber']=recruitNumberitem['workLocation']=workLocationitem['publishTime']=publishTimeyield itemnextFlag = response.xpath('//*[@id="next"]/@href')[0].extract()if 'start' in nextFlag:curpage = re.search('(\d+)',response.url).group(1)page =int(curpage)+10url = re.sub('\d+',str(page),response.url)print urlyield scrapy.Request(url, callback=self.parse)
在 settings 文件中,修改添加信息
LOG_FILE='tencent.log'
LOG_LEVEL='WARNING'
接下来执行:scrapy crawl tencent 。或者command line命令行执行:
scrapy crawl tencent --logfile 'tencent.log' -L WARNING
输出信息
print http://hr.tencent.com/position.php?&start=0
http://hr.tencent.com/position.php?&start=10
print http://hr.tencent.com/position.php?&start=10
http://hr.tencent.com/position.php?&start=20
print http://hr.tencent.com/position.php?&start=20
http://hr.tencent.com/position.php?&start=30
tencent.log 文件中记录,可以看到级别大于 INFO 日志输出
2016-08-15 23:22:59 [zhangsan] WARNING: WARNING on http://hr.tencent.com/position.php?&start=0
2016-08-15 23:22:59 [zhangsan] ERROR: info on http://hr.tencent.com/position.php?&start=0
2016-08-15 23:22:59 [zhangsan] WARNING: WARNING on http://hr.tencent.com/position.php?&start=10
2016-08-15 23:22:59 [zhangsan] ERROR: info on http://hr.tencent.com/position.php?&start=10
小试 LOG_STDOUT
settings.py
LOG_FILE='tencent.log'
LOG_STDOUT=True
LOG_LEVEL='INFO'
执行:scrapy crawl tencent
输出:空
tencent.log 文件中记录,可以看到级别大于 INFO 日志输出
2016-08-15 23:28:32 [stdout] INFO: http://hr.tencent.com/position.php?&start=110
2016-08-15 23:28:32 [stdout] INFO: print
2016-08-15 23:28:32 [stdout] INFO: http://hr.tencent.com/position.php?&start=110
2016-08-15 23:28:32 [zhangsan] INFO: info on http://hr.tencent.com/position.php?&start=110
2016-08-15 23:28:32 [zhangsan] WARNING: WARNING on http://hr.tencent.com/position.php?&start=110
2016-08-15 23:28:32 [zhangsan] ERROR: info on http://hr.tencent.com/position.php?&start=110
2016-08-15 23:28:32 [stdout] INFO: http://hr.tencent.com/position.php?&start=120
2016-08-15 23:28:33 [stdout] INFO: print
2016-08-15 23:28:33 [stdout] INFO: http://hr.tencent.com/position.php?&start=120
2016-08-15 23:28:33 [zhangsan] INFO: info on http://hr.tencent.com/position.php?&start=120
2016-08-15 23:28:33 [zhangsan] WARNING: WARNING on http://hr.tencent.com/position.php?&start=120
2016-08-15 23:28:33 [zhangsan] ERROR: info on http://hr.tencent.com/position.php?&start=120
scrapy 之 Logging 使用
#coding:utf-8
######################
##Logging的使用
######################
import logging
'''
1. logging.CRITICAL - for critical errors (highest severity) 致命错误
2. logging.ERROR - for regular errors 一般错误
3. logging.WARNING - for warning messages 警告+错误
4. logging.INFO - for informational messages 消息+警告+错误
5. logging.DEBUG - for debugging messages (lowest severity) 低级别
'''
logging.warning("This is a warning")logging.log(logging.WARNING,"This is a warning")#获取实例对象
logger=logging.getLogger()
logger.warning("这是警告消息")
#指定消息发出者
logger = logging.getLogger('SimilarFace')
logger.warning("This is a warning")#在爬虫中使用log
import scrapy
class MySpider(scrapy.Spider):name = 'myspider'start_urls = ['http://scrapinghub.com']def parse(self, response):#方法1 自带的loggerself.logger.info('Parse function called on %s', response.url)#方法2 自己定义个loggerlogger.info('Parse function called on %s', response.url)'''
Logging 设置
• LOG_FILE
• LOG_ENABLED
• LOG_ENCODING
• LOG_LEVEL
• LOG_FORMAT
• LOG_DATEFORMAT • LOG_STDOUT命令行中使用
--logfile FILE
Overrides LOG_FILE--loglevel/-L LEVEL
Overrides LOG_LEVEL--nolog
Sets LOG_ENABLED to False
'''import logging
from scrapy.utils.log import configure_loggingconfigure_logging(install_root_handler=False)
#定义了logging的些属性
logging.basicConfig(filename='log.txt',format='%(levelname)s: %(levelname)s: %(message)s',level=logging.INFO
)
#运行时追加模式
logging.info('进入Log文件')
logger = logging.getLogger('SimilarFace')
logger.warning("也要进入Log文件")
1.6 Settings
Scrapy 设置(settings)提供了定制 Scrapy 组件的方法。可以控制包括核心(core),插件(extension),pipeline 及 spider 组件。比如 设置 Json Pipeliine、LOG_LEVEL
内置设置列表请参考内置设置参考手册
获取设置值 (Populating the settings)
设置可以通过多种方式设置,每个方式具有不同的优先级。
下面以 优先级降序 的方式给出方式列表:
- 命令行选项(Command line Options)(最高优先级) 。命令行传入的参数具有最高的优先级。 使用选项 -s (或 --set) 来覆盖一个 (或更多) 选项。比如:scrapy crawl myspider -s LOG_FILE=scrapy.log
- 每个 spider 的设置 ( scrapy.spiders.Spider.custom_settings )。
class MySpider(scrapy.Spider):name = 'myspider'custom_settings = {'SOME_SETTING': 'some value',}
- 项目设置模块 (Project settings module)。项目设置模块是 Scrapy 项目的标准配置文件。即 setting.py
myproject.settings
如何访问配置 (settings)
In a spider, the settings are available through self.settings:
class MySpider(scrapy.Spider):name = 'myspider'start_urls = ['http://example.com']def parse(self, response):print("Existing settings: %s" % self.settings.attributes.keys())
Settings can be accessed through the scrapy.crawler.Crawler.settings attribute of the Crawler that is passed to from_crawler method in extensions, middlewares and item pipelines:
class MyExtension(object):def __init__(self, log_is_enabled=False):if log_is_enabled:print("log is enabled!")@classmethoddef from_crawler(cls, crawler):settings = crawler.settingsreturn cls(settings.getbool('LOG_ENABLED'))
案例 ( self.settings 使用 )
添加一行代码 print("Existing settings: %s" % self.settings['LOG_FILE'])
# -*- coding:utf-8 -*-
import scrapy
from tutorial.items import RecruitItem
from scrapy.contrib.spiders import CrawlSpider, Rule
from scrapy.linkextractors import LinkExtractor
import loggingclass RecruitSpider(CrawlSpider):name = "tencent_crawl"allowed_domains = ["hr.tencent.com"]start_urls = ["http://hr.tencent.com/position.php?&start=0#a"]#提取匹配 'http://hr.tencent.com/position.php?&start=\d+'的链接page_lx = LinkExtractor(allow=('start=\d+'))rules = [#提取匹配,并使用spider的parse方法进行分析;并跟进链接(没有callback意味着follow默认为True)Rule(page_lx, callback='parseContent',follow=True)]def parseContent(self, response):print response.urlprint("Existing settings: %s" % self.settings['LOG_FILE'])self.logger.info('Parse function called on %s', response.url)for sel in response.xpath('//*[@class="even"]'):name = sel.xpath('./td[1]/a/text()').extract()[0]detailLink = sel.xpath('./td[1]/a/@href').extract()[0]catalog =Noneif sel.xpath('./td[2]/text()'):catalog = sel.xpath('./td[2]/text()').extract()[0]recruitNumber = sel.xpath('./td[3]/text()').extract()[0]workLocation = sel.xpath('./td[4]/text()').extract()[0]publishTime = sel.xpath('./td[5]/text()').extract()[0]#print name, detailLink, catalog,recruitNumber,workLocation,publishTimeitem = RecruitItem()item['name']=name.encode('utf-8')item['detailLink']=detailLink.encode('utf-8')if catalog:item['catalog']=catalog.encode('utf-8')item['recruitNumber']=recruitNumber.encode('utf-8')item['workLocation']=workLocation.encode('utf-8')item['publishTime']=publishTime.encode('utf-8')yield item
内置设置参考手册
BOT_NAME:默认: 'scrapybot'。当您使用 startproject 命令创建项目时其也被自动赋值。
CONCURRENT_ITEMS:默认: 100。Item Processor(即 Item Pipeline) 同时处理(每个response的)item的最大值。
CONCURRENT_REQUESTS:默认: 16。Scrapy downloader 并发请求(concurrent requests)的最大值。
DEFAULT_REQUEST_HEADERS 默认:
{'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8','Accept-Language': 'en',
}
Scrapy HTTP Request使用的默认header。
DEPTH_LIMIT:默认: 0。爬取网站最大允许的深度(depth)值。如果为0,则没有限制。
DOWNLOAD_DELAY:默认: 0。下载器在下载同一个网站下一个页面前需要等待的时间。该选项可以用来限制爬取速度, 减轻服务器压力。同时也支持小数:
DOWNLOAD_DELAY = 0.25 # 250 ms of delay:
该设置影响(默认启用的) RANDOMIZE_DOWNLOAD_DELAY 设置。 默认情况下,Scrapy在两个请求间不等待一个固定的值, 而是使用0.5到1.5之间的一个随机值 * DOWNLOAD_DELAY 的结果作为等待间隔。
DOWNLOAD_TIMEOUT:默认: 180。下载器超时时间(单位: 秒)。
ITEM_PIPELINES:默认: {}。保存项目中启用的pipeline及其顺序的字典。该字典默认为空,值(value)任意。 不过值(value)习惯设置在0-1000范围内。
样例:
ITEM_PIPELINES = {
'mybot.pipelines.validate.ValidateMyItem': 300,
'mybot.pipelines.validate.StoreMyItem': 800,
}
LOG_ENABLED:默认: True。是否启用logging。
LOG_ENCODING:默认: 'utf-8'。logging使用的编码。
LOG_LEVEL:默认: 'DEBUG'。log的最低级别。可选的级别有: CRITICAL、 ERROR、WARNING、INFO、DEBUG 。
USER_AGENT:默认: "Scrapy/VERSION (+http://scrapy.org)"。爬取的默认User-Agent,除非被覆盖。
1.7 阳光热线问政平台( 东莞 )
目标网址:http://wz.sun0769.com/political/index/politicsNewest?id=1&type=4
items.py:添加以下代码
from scrapy.item import Item, Fieldclass SunItem(Item):number = Field()url = Field()title = Field()content = Field()
在 spiders 目录下新建一个自定义 SunSpider.py
from scrapy.spiders import CrawlSpider, Rule
from scrapy.linkextractors import LinkExtractor
# from tutorial.items import SunItem
import scrapy
import urllib
import time
import reclass SunSpider(CrawlSpider):name = 'sun0769'num = 0allow_domain = ['http://wz.sun0769.com/']start_urls = ['http://wz.sun0769.com/political/index/politicsNewest?id=1&type=4']rules = {Rule(LinkExtractor(allow='page'), process_links='process_request', follow=True),Rule(LinkExtractor(allow=r'/html/question/\d+/\d+\.shtml$'), callback='parse_content')}def process_request(self, links):ret = []for link in links:try:page = re.search(r'page=\d*', link.url).group()tp = re.search(r'type=\d+', link.url).group()link.url = 'http://wz.sun0769.com/index.php/question/questionType?' + page + "&" + tpexcept BaseException as e:print(e)ret.append(link)return retdef parse_content(self, response):item = SunItem()url = response.urltitle = response.xpath('//*[@class="greyframe"]/div/div/strong/text()')[0].extract().strip()number = response.xpath('//*[@class="greyframe"]/div/div/strong/text()')[0].extract().strip().split(':')[-1]content = response.xpath('//div[@class="c1 text14_2"]/text()').extract()[0].strip()item['url'] = urlitem['title'] = titleitem['number'] = numberitem['content'] = contentprint(dict(item))# yield itemif __name__ == '__main__':from scrapy import cmdlinecmdline.execute('scrapy crawl sun0769'.split())pass
在 pipelines.py:添加如下代码
import json
import codecsclass JsonWriterPipeline(object):def __init__(self):self.file = codecs.open('items.json', 'w', encoding='utf-8')def process_item(self, item, spider):line = json.dumps(dict(item), ensure_ascii=False) + "\n"self.file.write(line)return itemdef spider_closed(self, spider):self.file.close()
settings.py 添加如下代码(启用组件)
ITEM_PIPELINES = {'tutorial.pipelines.JsonWriterPipeline': 300,
}
window 下调试
在项目根目录下新建 main.py 文件,用于调试
from scrapy import cmdline
cmdline.execute('scrapy crawl sun0769'.split())
2. scrapy 案例 和 scrapyd 部署
案例 1:腾讯招聘
腾讯招聘:https://careers.tencent.com/search.html
items.py:添加以下代码
from scrapy.item import Item, Fieldclass TencentItem(Item):title = Field()catalog = Field()workLocation = Field()recruitNumber = Field()duty = Field()Job_requirement= Field()url = Field()publishTime = Field()
在 spiders 目录下新建一个自定义 tencent_info.py
# -*- coding:utf-8 -*-
from scrapy.selector import Selector
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
import re
from tutorial.items import TencentItemclass TencentSpider(CrawlSpider):name = "tengxun_info"allowed_domains = ["tencent.com"]start_urls = ["http://hr.tencent.com/position.php"]rules = [Rule(LinkExtractor(allow=("start=\d+"))),Rule(LinkExtractor(allow=("position_detail\.php")), follow=True, callback='parse_item')]def parse_item(self,response):item =TencentItem()title = response.xpath('//*[@id="sharetitle"]/text()')[0].extract()workLocation = response.xpath('//*[@class="lightblue l2"]/../text()')[0].extract()catalog = response.xpath('//*[@class="lightblue"]/../text()')[0].extract()recruitNumber = response.xpath('//*[@class="lightblue"]/../text()').re('(\d+)')[0]duty_pre = response.xpath('//*[@class="squareli"]')[0].extract()duty = re.sub('<.*?>','',duty_pre)Job_requirement_pre = response.xpath('//*[@class="squareli"]')[1].extract()Job_requirement = re.sub('<.*?>','',Job_requirement_pre)item['title']=titleitem['url']=response.urlitem['workLocation']=workLocationitem['catalog']=catalogitem['recruitNumber']=recruitNumberitem['duty']=dutyitem['Job_requirement']=Job_requirementyield item
在 pipelines.py:添加如下代码
import json
import codecsclass JsonWriterPipeline(object):def __init__(self):self.file = codecs.open('items.json', 'w', encoding='utf-8')def process_item(self, item, spider):line = json.dumps(dict(item), ensure_ascii=False) + "\n"self.file.write(line)return itemdef spider_closed(self, spider):self.file.close()
settings.py 添加如下代码(启用组件)
ITEM_PIPELINES = {'tutorial.pipelines.JsonWriterPipeline': 300,
}
在项目根目录下新建 main.py 文件,用于调试
from scrapy import cmdline
cmdline.execute('scrapy crawl tengxun_info'.split())
案例 2:国家食品药品监督管理总局
目标网站( 药品 ---> 国产药品 ):https://www.nmpa.gov.cn/
抓包结果如图:
items.py:添加以下代码
from scrapy import Field
import scrapyclass Sfda1Item(scrapy.Item):# define the fields for your item here like:data = scrapy.Field()
在spiders目录下新建一个自定义spider
# -*- coding: utf-8 -*-
import scrapy
from scrapy.http import FormRequest
# from tutorial.items import Sfda1Item
import urllib
import reclass sfdaSpider(scrapy.Spider):name = 'sfda'allowed_domains = ['sfda.gov.cn']def start_requests(self):url = 'http://app1.sfda.gov.cn/datasearch/face3/search.jsp'data = {'tableId': '32','State': '1','bcId': '124356639813072873644420336632','State': '1','tableName': 'TABLE32','State': '1','viewtitleName': 'COLUMN302','State': '1','viewsubTitleName': 'COLUMN299,COLUMN303','State': '1','curstart': '1','State': '1','tableView': urllib.quote("国产药品商品名"),'State': '1',}yield FormRequest(url=url, formdata=data, meta={'data': data}, callback=self.parseContent)def parseContent(self, response):for site in response.xpath('//a').re(r'callbackC,\'(.*?)\',null'):id = re.search('.+Id=(.*?)$', site).group(1)# print idurl = 'http://app1.sfda.gov.cn/datasearch/face3/content.jsp?tableId=32&tableName=TABLE32' \'&tableView=%B9%FA%B2%FA%D2%A9%C6%B7%C9%CC%C6%B7%C3%FB&Id=' + idyield scrapy.Request(url,headers={'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 ''(KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36'},callback=self.ParseDetail)data = response.meta['data']data['curstart'] = str(int(data['curstart']) + 1)yield FormRequest(url=response.request.url, formdata=data, meta={'data': data}, callback=self.parseContent)def ParseDetail(self, response):item = dict()for site in response.xpath('//table[1]/.//tr')[1:-1]:try:if not site.xpath('./td/text()').extract()[0]:continuename = site.xpath('./td/text()').extract()[0]value = re.sub('<.*?>', '', site.xpath('./td')[1].extract()).strip()print(name, value)item[name] = valueexcept BaseException as e:print('error', e)# sfa = Sfda1Item()item_data = dict()item_data['data'] = itemyield item_dataif __name__ == '__main__':from scrapy import cmdlinecmdline.execute('scrapy crawl sfda'.split())pass
在 pipelines.py:添加如下代码
import json
import codecsclass JsonWriterPipeline(object):def __init__(self):self.file = codecs.open('items.json', 'w', encoding='utf-8')def process_item(self, item, spider):line = json.dumps(dict(item), ensure_ascii=False) + "\n"self.file.write(line)return itemdef spider_closed(self, spider):self.file.close()
settings.py:添加如下代码(启用组件)
ITEM_PIPELINES = {'tutorial.pipelines.JsonWriterPipeline': 300,
}
在项目根目录下新建main.py文件,用于调试
from scrapy import cmdline
cmdline.execute('scrapy crawl sfda -L INFO'.split())
3. 使用 scrapyd 管理爬虫
scrapyd 是由 scrapy 官方提供的爬虫管理工具,使用它我们可以非常方便地上传、控制爬虫并且查看运行日志。
参考官方文档 : http://scrapyd.readthedocs.org/en/latest/api.html
使用 scrapyd 和我们直接运行 scrapy crawl myspider 有什么区别呢?scrapyd 同样是通过上面的命令运行爬虫的,不同的是它提供一个JSON web service 监听的请求,我们可以从任何一台可以连接到服务器的电脑发送请求安排爬虫运行,或者停止正在运行的爬虫。甚至,我们可以使用它提供的API上传新爬虫而不必登录到服务器上进行操作。
安装 scrapyd:pip install scrapyd
参考文档:https://github.com/scrapy/scrapyd-client
运行 scrapyd 服务。直接运行命令 scrapyd 即可:scrapyd
默认情况下scrapyd 监听 0.0.0.0:6800 端口,运行 scrapyd 后在浏览器 http://localhost:6800/ 即可查看到当前可以运行的项目:
web 接口:http://localhost:6800/
部署 scrapy 项目:直接使用 scrapyd-client 提供的 scrapyd-deploy 工具:pip install scrapyd-client
直接在项目根目录:修改工程目录下的 scrapy.cfg 文件
[deploy:scrapyd2] #默认情况下并没有scrapyd2,它只是一个名字,可以在配置文件中写多个名字不同的deploy
url = http://scrapyd.mydomain.com/api/scrapyd/ #要部署项目的服务器的地址
username = john #访问服务器所需的用户名和密码(如果不需要密码可以不写)
password = secret
其中的 username 和 password 用于在部署时验证服务器的 HTTP basic authentication,须要注意的是这里的用户密码并不表示访问该项目须要验证,而是登录服务器用的。Ubuntu/Windows:
[deploy:tutorial_deploy]
url = http://192.168.17.129:6800/
project = tutorial
username = enlong
password = test
部署项目到服务器:直接在项目根目录:
Windows:python c:\Python27\Scripts\scrapyd-deploy
Ubuntu:scrapyd-deploy tutorial_deploy -p tutorial
部署操作会打包你的当前项目,如果当前项目下有setup.py文件,就会使用它,没有的会就会自动创建一个。(如果后期项目需要打包的话,可以根据自己的需要修改里面的信息,也可以暂时不管它)。从返回的结果里面,可以看到部署的状态,项目名称,版本号和爬虫个数,以及当前的主机名称。
查看项目 spider
通过 scrapyd-deploy -l 查看当前目录下的可以使用的部署方式(target)
Windows/Ubuntu
scrapy list
scrapyd-deploy -l
或再次打开 http://localhost:6800/, 也可以看到 Available projects: default, tutorial
列出服务器上所有的项目,检查tutorial_deploy 是否已经部署上去了:
scrapyd-deploy -L tutorial_deploy
default
tutorial
API
scrapyd 的 web 界面比较简单,主要用于监控,所有的调度工作全部依靠接口实现.
参考官方文档:http://scrapyd.readthedocs.org/en/stable/api.html
开启爬虫 schedule
curl http://localhost:6800/schedule.json -d project=tutorial -d spider=tencentWindows/Ubuntu 注意:执行时 cd 到项目根目录执行curl http://localhost:6800/schedule.json -d project=tutorial -d spider=tencent
{"status": "ok", "jobid": "94bd8ce041fd11e6af1a000c2969bafd", "node_name": "ubuntu"}
停止 cancel
curl http://localhost:6800/cancel.json -d project=tutorial -d job=94bd8ce041fd11e6af1a000c2969bafd
列出爬虫
curl http://localhost:6800/listspiders.json?project=tutorial
删除项目
curl http://localhost:6800/delproject.json -d project=tutorial
更新
对于 scrapyd 默认项目 (即是启动 scrapyd 命令后看到的default项目):
只有在 scrapy 项目里启动 scrapyd 命令时才有默认项目,默认项目就是当前的 scrapy 项目
如果在非 scrapy 项目下执行 scrapyd, 是看不到 default 的
注意:执行时 cd 到项目根目录执行
第一种 情况
cfg:[deploy]
url = http://192.168.17.129:6800/
project = tutorial
username = enlong
password = test运行结果:python@ubuntu:~/project/tutorial$ scrapyd-deploy
Packing version 1471069533
Deploying to project "tutorial" in http://192.168.17.129:6800/addversion.json
Server response (200):
{"status": "ok", "project": "tutorial", "version": "1471069533", "spiders": 1, "node_name": "ubuntu"}
第二种情况
cfg:[deploy:tutorial_deploy]
url = http://192.168.17.129:6800/
project = tutorial
username = enlong
password = test运行结果:python@ubuntu:~/project/tutorial$ scrapyd-deploy tutorial_deploy
Packing version 1471069591
Deploying to project "tutorial" in http://192.168.17.129:6800/addversion.json
Server response (200):
{"status": "ok", "project": "tutorial", "version": "1471069591", "spiders": 1, "node_name": "ubuntu"}
4. 为 scrapyd 创建服务
Systemd 是 Linux 系统工具,用来启动守护进程,已成为大多数发行版的标准配置。首先检查你的系统中是否安装有 systemd 并确定当前安装的版本
Systemd 入门教程:命令篇:http://www.ruanyifeng.com/blog/2016/03/systemd-tutorial-commands.html
Systemd 入门教程:实战篇:http://www.ruanyifeng.com/blog/2016/03/systemd-tutorial-part-two.html
systemd --version
sudo vi /lib/systemd/system/scrapyd.service
Then add the following line into that file:
[Unit]
Description=scrapyd
After=network.target
Documentation=http://scrapyd.readthedocs.org/en/latest/api.html[Service]
User=root
ExecStart=/usr/local/bin/scrapyd --logfile /var/scrapyd/scrapyd.log[Install]
WantedBy=multi-user.target
[Unit]区块通常是配置文件的第一个区块,用来定义 Unit 的元数据,以及配置与其他 Unit 的关系
After:如果该字段指定的 Unit After 也要启动,那么必须在当前 service 之前启动
Documentation:服务文档地址
Description:简短描述
[Service]区块用来 Service 的配置,只有 Service 类型的 Unit 才有这个区块
ExecStart:启动当前服务的命令
[Install]通常是配置文件的最后一个区块,用来定义如何启动,以及是否开机启动
WantedBy:它的值是一个或多个 Target,当前 Unit 激活时(enable)符号链接会放入/etc/systemd/system目录下面以 Target 名 + .wants后缀构成的子目录中
At this point, we can now start our new service by running the command below:
sudo systemctl start scrapyd
sudo service scrapyd start
To check the status of the service, issue the following command:
sudo systemctl status scrapyd
开机启动
To make the services start at boot time, use the command below:
sudo systemctl enable scrapyd
Created symlink from /etc/systemd/system/multi-user.target.wants/scrapyd.service to /lib/systemd/system/scrapyd.service.
取消开机启动
sudo systemctl disable scrapyd
5. scrapyd 服务器添加认证信息
我们也可以在scrapyd前面加一层反向代理来实现用户认证。以nginx为例, 配置nginx
安装 nginx:sudo apt-get install nginx
配置 nginx:vi /etc/nginx/nginx.conf 修改如下:
# Scrapyd local proxy for basic authentication.
# Don't forget iptables rule.
# iptables -A INPUT -p tcp --destination-port 6800 -s ! 127.0.0.1 -j DROPhttp {server {listen 6801;location / {proxy_pass http://127.0.0.1:6800/;auth_basic "Restricted";auth_basic_user_file /etc/nginx/conf.d/.htpasswd;}}
}
/etc/nginx/htpasswd/user.htpasswd
里设置的用户名 enlong和密码都是test 修改配置文件,添加用户信息
Nginx 使用 htpasswd 创建用户认证
python@ubuntu:/etc/nginx/conf.d$ sudo htpasswd -c .htpasswd enlong
New password:
Re-type new password:
Adding password for user enlong
python@ubuntu:/etc/nginx/conf.d$ cat .htpasswd
enlong:$apr1$2slPhvee$6cqtraHxoxclqf1DpqIPM.python@ubuntu:/etc/nginx/conf.d$ sudo htpasswd -bc .htpasswd admin admin
apache htpasswd 命令用法实例
1、如何利用 htpasswd 命令添加用户?
htpasswd -bc .passwd www.leapsoul.cn php
在bin目录下生成一个.passwd文件,用户名www.leapsoul.cn,密码:php,默认采用MD5加密方式
2、如何在原有密码文件中增加下一个用户?
htpasswd -b .passwd leapsoul phpdev
去掉c选项,即可在第一个用户之后添加第二个用户,依此类推
重启 nginx:sudo service nginx restart
测试 Nginx
F:\_____gitProject_______\curl-7.33.0-win64-ssl-sspi\tieba_baidu>curl http://localhost:6800/schedule.json -d project=tutorial -d spider=tencent -u enlong:test
{"status": "ok", "jobid": "5ee61b08428611e6af1a000c2969bafd", "node_name": "ubuntu"}
配置 scrapy.cfg 文件
[deploy]
url = http://192.168.17.129:6801/
project = tutorial
username = admin
password = admin
注意上面的url
已经修改为了nginx监听的端口。
提醒: 记得修改服务器上scrapyd的配置bind_address
字段为127.0.0.1
,以免可以从外面绕过nginx, 直接访问6800端口。 关于配置可以参看本文后面的配置文件设置.
修改配置文件:sudo vi /etc/scrapyd/scrapyd.conf
[scrapyd]
bind_address = 127.0.0.1
scrapyd
启动的时候会自动搜索配置文件,配置文件的加载顺序为
/etc/scrapyd/scrapyd.conf
/etc/scrapyd/conf.d/*
scrapyd.conf
~/.scrapyd.conf
最后加载的会覆盖前面的设置
默认配置文件如下, 可以根据需要修改
[scrapyd]
eggs_dir = eggs
logs_dir = logs
items_dir = items
jobs_to_keep = 5
dbs_dir = dbs
max_proc = 0
max_proc_per_cpu = 4
finished_to_keep = 100
poll_interval = 5
bind_address = 0.0.0.0
http_port = 6800
debug = off
runner = scrapyd.runner
application = scrapyd.app.application
launcher = scrapyd.launcher.Launcher[services]
schedule.json = scrapyd.webservice.Schedule
cancel.json = scrapyd.webservice.Cancel
addversion.json = scrapyd.webservice.AddVersion
listprojects.json = scrapyd.webservice.ListProjects
listversions.json = scrapyd.webservice.ListVersions
listspiders.json = scrapyd.webservice.ListSpiders
delproject.json = scrapyd.webservice.DeleteProject
delversion.json = scrapyd.webservice.DeleteVersion
listjobs.json = scrapyd.webservice.ListJobs
关于配置的各个参数具体含义,可以参考官方文档:http://scrapyd.readthedocs.io/en/stable/config.html
采集感兴趣的网站:(1)京东 (2)豆瓣 (3)论坛 。。。。