传智播客爬虫学习笔记。。
1.Scrapy介绍
1.1 Scrapy框架
- Scrapy是用纯Python实现一个为了爬取网站数据、提取结构性数据而编写的应用框架,用途非常广泛。
- 框架的力量,用户只需要定制开发几个模块就可以轻松的实现一个爬虫,用来抓取网页内容以及各种图片,非常之方便。
- Scrapy 使用了 Twisted['twɪstɪd](其主要对手是Tornado)异步网络框架来处理网络通讯,可以加快我们的下载速度,不用自己去实现异步框架,并且包含了各种中间件接口,可以灵活的完成各种需求。
1.2 Scrapy架构图(绿线是数据流向):
- Scrapy Engine(引擎): 负责Spider、ItemPipeline、Downloader、Scheduler中间的通讯,信号、数据传递等。
- Scheduler(调度器): 它负责接受引擎发送过来的Request请求,并按照一定的方式进行整理排列,入队,当引擎需要时,交还给引擎。
- Downloader(下载器):负责下载Scrapy Engine(引擎)发送的所有Requests请求,并将其获取到的Responses交还给Scrapy Engine(引擎),由引擎交给Spider来处理,
- Spider(爬虫):它负责处理所有Responses,从中分析提取数据,获取Item字段需要的数据,并将需要跟进的URL提交给引擎,再次进入Scheduler(调度器),
- Item Pipeline(管道):它负责处理Spider中获取到的Item,并进行进行后期处理(详细分析、过滤、存储等)的地方.
- Downloader Middlewares(下载中间件):你可以当作是一个可以自定义扩展下载功能的组件。
- Spider Middlewares(Spider中间件):你可以理解为是一个可以自定扩展和操作引擎和Spider中间通信的功能组件(比如进入Spider的Responses;和从Spider出去的Requests)
1.3 Scrapy的运作流程
- 引擎:Hi!Spider, 你要处理哪一个网站?
- Spider:老大要我处理xxxx.com。
- 引擎:你把第一个需要处理的URL给我吧。
- Spider:给你,第一个URL是xxxxxxx.com。
- 引擎:Hi!调度器,我这有request请求你帮我排序入队一下。
- 调度器:好的,正在处理你等一下。
- 引擎:Hi!调度器,把你处理好的request请求给我。
- 调度器:给你,这是我处理好的request
- 引擎:Hi!下载器,你按照老大的下载中间件的设置帮我下载一下这个request请求
- 下载器:好的!给你,这是下载好的东西。(如果失败:sorry,这个request下载失败了。然后引擎告诉调度器,这个request下载失败了,你记录一下,我们待会儿再下载)
- 引擎:Hi!Spider,这是下载好的东西,并且已经按照老大的下载中间件处理过了,你自己处理一下(注意!这儿responses默认是交给def parse()这个函数处理的)
- Spider:(处理完毕数据之后对于需要跟进的URL),Hi!引擎,我这里有两个结果,这个是我需要跟进的URL,还有这个是我获取到的Item数据。
- 引擎:Hi !管道 我这儿有个item你帮我处理一下!调度器!这是需要跟进URL你帮我处理下。然后从第四步开始循环,直到获取完老大需要全部信息。
- 管道``调度器:好的,现在就做!
注意!只有当调度器中不存在任何request了,整个程序才会停止,(也就是说,对于下载失败的URL,Scrapy也会重新下载。)
1.4 Scrapy 爬虫步骤
- 新建项目 (scrapy startproject xxx):新建一个新的爬虫项目
- 明确目标 (编写items.py):明确你想要抓取的目标
- 制作爬虫 (spiders/xxspider.py):制作爬虫开始爬取网页
- 存储内容 (pipelines.py):设计管道存储爬取内容
2.入门案例
2.1学习目标
- 创建一个Scrapy项目
- 定义提取的结构化数据(Item)
- 编写爬取网站的 Spider 并提取出结构化数据(Item)
- 编写 Item Pipelines 来存储提取到的Item(即结构化数据)
2.2. 新建项目(scrapy startproject)
在开始爬取之前,必须创建一个新的Scrapy项目。进入项目目录,运行如下命令。
scrapy startproject mySpider
其中的mySpider是项目的名称,可以看到创建了一个mySpider文件夹,如上图。
各个主要文件的作用:
- scrapy.cfg:爬虫程序的配置文件
- mySpider/:项目的Python模块,在这写代码
- mySpider/items:项目的目标文件
- mySpider/pipelines.py :项目的管道文件
- mySpider/settings.py :项目的设置文件
- mySpider/spiders/ :存储爬虫代码目录
2.3明确目标(mySpider/items.py)
url:http://www.itcast.cn/channel/teacher.shtml
抓取网站里的所有讲师的姓名、职称和个人信息。
(1)打开mySpider目录下的items.py
(2)Item 定义结构化数据字段,用来保存爬取到的数据,有点像Python中的dict,但是提供了一些额外的保护减少错误。
(3)可以通过创建一个 scrapy.Item 类, 并且定义类型为 scrapy.Field的类属性来定义一个Item(可以理解成类似于ORM的映射关系)。
(4)接下来,创建一个ItcastItem 类,和构建item模型(model)。
import scrapy
class ItcastItem(scrapy.Item):
name = scrapy.Field()
title = scrapy.Field()
info = scrapy.Field()
2.4 编写爬虫 (spiders/itcastSpider.py)
爬虫功能要分两步:
2.4.1 爬数据
(1) 点击开始,输入cmd,点击回车进入doc命令窗口,进入爬虫程序目录下的myspider路径,在当前目录下输入命令“scrapy genspider itcast "itcast.cn"
”创建一个名字为itcast的爬虫,并且指定爬取区域的范围,创建完成后入下图所示:
(2)打开 mySpider/spider目录里的 itcast.py,默认增加了下列代码:
class ItcastSpider(scrapy.Spider):
name = "itcast"
allowed_domains = ["itcast.cn"]
start_urls = (
'http://www.itcast.cn/',
)
def parse(self, response):
pass
PS:要建立一个Spider, 你必须用scrapy.Spider类创建一个子类,并确定了三个强制的属性 和 一个方法。
name = ""
:这个爬虫的识别名称,必须是唯一的,在不同的爬虫必须定义不同的名字。allow_domains = []
是搜索的域名范围,也就是爬虫的约束区域,规定爬虫只爬取这个域名下的网页,不存在的URL会被忽略。start_urls = ()
:爬取的URL元祖/列表。爬虫从这里开始抓取数据,所以,第一次下载的数据将会从这些urls开始。其他子URL将会从这些起始URL中继承性生成。parse(self, response)
:解析的方法,每个初始URL完成下载后将被调用,调用的时候传入从每一个URL传回的Response对象来作为唯一参数,主要作用如下:
1. 负责解析返回的网页数据(response.body),提取结构化数据(生成item)
2.生成需要下一页的URL请求。
(3)将start_urls的值修改为需要爬取的第一个url:
start_urls = ("http://www.itcast.cn/channel/teacher.shtml",)
(4)修改parse()方法
def parse(self, response):
filename =
"teacher.html"
open(filename,
'w').write(response.body)
5) 运行爬虫
在爬虫主目录下新建begin.py文件,在文件中输入如下代码,运行该文件,爬虫运行。
from scrapy import cmdline cmdline.execute("scrapy crawl itcast".split())
itcast代表爬虫名字,运行之后,如果打印的日志出现 [scrapy] INFO: Spider closed (finished)
,代表执行完成。 之后当前文件夹中就出现了一个 teacher.html 文件,里面就是我们刚刚要爬取的网页的全部源代码信息。
2.4.2 取数据
爬取整个网页完毕,接下来的就是的取过程了,首先观察页面源码:
<div class="li_txt"> <h3> xxx </h3> <h4> xxxxx </h4> <p> xxxxxxxx </p>
(1)我们之前在mySpider/items.py 里定义了一个ItcastItem类。 这里引入进来
from myspider.items import ItcastItem
(2)然后将我们得到的数据封装到一个ItcastItem
对象中,可以保存每个老师的属性:
from mySpider.items import ItcastItem def parse(self, response): #open("teacher.html","wb").write(response.body).close() # 存放老师信息的集合 items = [] for each in response.xpath("//div[@class='li_txt']"): # 将我们得到的数据封装到一个 `ItcastItem` 对象 item = ItcastItem() #extract()方法返回的都是unicode字符串 name = each.xpath("h3/text()").extract() title = each.xpath("h4/text()").extract() info = each.xpath("p/text()").extract() #xpath返回的是包含一个元素的列表 item['name'] = name[0] item['title'] = title[0] item['info'] = info[0] items.append(item) # 直接返回最后数据 return items
2.4.3保存数据
scrapy保存信息的最简单的方法主要有四种,-o 输出指定格式的文件,,命令如下:
# json格式,默认为Unicode编码 scrapy crawl itcast -o teachers.json # json lines格式,默认为Unicode编码 scrapy crawl itcast -o teachers.jsonl # csv 逗号表达式,可用Excel打开 scrapy crawl itcast -o teachers.csv # xml格式 scrapy crawl itcast -o teachers.xml
2.4.4 拓展
请思考 yield 在这里的作用:
from mySpider.items import ItcastItem def parse(self, response): #open("teacher.html","wb").write(response.body).close() # 存放老师信息的集合 #items = [] for each in response.xpath("//div[@class='li_txt']"): # 将我们得到的数据封装到一个 `ItcastItem` 对象 item = ItcastItem() #extract()方法返回的都是unicode字符串 name = each.xpath("h3/text()").extract() title = each.xpath("h4/text()").extract() info = each.xpath("p/text()").extract() #xpath返回的是包含一个元素的列表 item['name'] = name[0] item['title'] = title[0] item['info'] = info[0] #items.append(item) #将获取的数据交给pipelines yield item # 返回数据,不经过pipeline #return items
3. Scrapy Shell
Scrapy终端是一个交互终端,我们可以在未启动spider的情况下尝试及调试代码,也可以用来测试XPath或CSS表达式,查看他们的工作方式,方便我们爬取的网页中提取的数据。如果安装了 IPython ,Scrapy终端将使用 IPython (替代标准Python终端)。 IPython 终端与其他相比更为强大,提供智能的自动补全,高亮输出,及其他特性。(推荐安装IPython)
3.1 启动Scrapy Shell
进入项目的根目录,执行下列命令来启动shell:
scrapy shell "http://www.itcast.cn/channel/teacher.shtml"
Scrapy Shell根据下载的页面会自动创建一些方便使用的对象,例如 Response 对象,以及Selector
对象 (对HTML及XML内容)
。
- 当shell载入后,将得到一个包含response数据的本地 response 变量,输入
response.body
将输出response的包体,输出response.headers
可以看到response的包头。 - 输入
response.selector
时, 将获取到一个response 初始化的类 Selector 的对象,此时可以通过使用response.selector.xpath()
或response.selector.css()
来对 response 进行查询。 - Scrapy也提供了一些快捷方式, 例如
response.xpath()
或response.css()
同样可以生效(如之前的案例)。
3.2 Selectors选择器
Scrapy Selectors 内置 XPath 和 CSS Selector 表达式机制
(1)Selector有四个基本的方法,最常用的还是xpath:
xpath(): 传入xpath表达式,返回该表达式所对应的所有节点的selector list列表
extract(): 序列化该节点为Unicode字符串并返回list
css(): 传入CSS表达式,返回该表达式所对应的所有节点的selector list列表,语法同 BeautifulSoup4
re(): 根据传入的正则表达式对数据进行提取,返回Unicode字符串list列表
(2)XPath表达式的例子及对应的含义:
/html/head/title: 选择<HTML>文档中 <head> 标签内的 <title> 元素 /html/head/title/text(): 选择上面提到的 <title> 元素的文字 //td: 选择所有的 <td> 元素 //div[@class="mine"]: 选择所有具有 class="mine" 属性的 div 元素
3.3尝试Selector
腾讯社招的网站http://hr.tencent.com/position.php?&start=0#a举例:
# 启动 scrapy shell "http://hr.tencent.com/position.php?&start=0#a" # 返回 xpath选择器对象列表 response.xpath('//title') [<Selector xpath='//title' data=u'<title>\u804c\u4f4d\u641c\u7d22 | \u793e\u4f1a\u62db\u8058 | Tencent \u817e\u8baf\u62db\u8058</title'>] # 使用 extract()方法返回 Unicode字符串列表 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> # 返回 xpath选择器对象列表 response.xpath('//title/text()') <Selector xpath='//title/text()' data=u'\u804c\u4f4d\u641c\u7d22 | \u793e\u4f1a\u62db\u8058 | Tencent \u817e\u8baf\u62db\u8058'> # 返回列表第一个元素的Unicode字符串 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('//*[@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] 技术类 以后做数据提取的时候,可以把现在Scrapy Shell中测试,测试通过后再应用到代码中。
4.Item Pipeline
当Item在Spider中被收集之后,它将会被传递到Item Pipeline,这些Item Pipeline组件按定义的顺序处理Item。
每个Item Pipeline都是实现了简单方法的Python类,比如决定此Item是丢弃而存储。以下是item pipeline的一些典型应用:
- 验证爬取的数据(检查item包含某些字段,比如说name字段)
- 查重(并丢弃)
- 将爬取结果保存到文件或者数据库中
4.1 编写item pipeline
编写item pipeline很简单,item pipiline组件是一个独立的Python类,其中process_item()方法必须实现:
import something class SomethingPipeline(object): def __init__(self): # 可选实现,做参数初始化等 # doing something def process_item(self, item, spider): # item (Item 对象) – 被爬取的item # spider (Spider 对象) – 爬取该item的spider # 这个方法必须实现,每个item pipeline组件都需要调用该方法, # 这个方法必须返回一个 Item 对象,被丢弃的item将不会被之后的pipeline组件所处理。 return item def open_spider(self, spider): # spider (Spider 对象) – 被开启的spider # 可选实现,当spider被开启时,这个方法被调用。 def close_spider(self, spider): # spider (Spider 对象) – 被关闭的spider # 可选实现,当spider被关闭时,这个方法被调用
4.2 item写入JSON文件
以下pipeline将所有(从所有'spider'中)爬取到的item,存储到一个独立地teacher.json 文件,每行包含一个序列化为'JSON'格式的'item':
import json class ItcastJsonPipeline(object): def __init__(self): self.file = open('teacher.json', 'wb') def process_item(self, item, spider): content = json.dumps(dict(item), ensure_ascii=False) + "\n" self.file.write(content) return item def close_spider(self, spider): self.file.close()
4.3 启用一个Item Pipeline组件
为了启用Item Pipeline组件,必须将它的类添加到 settings.py文件ITEM_PIPELINES 配置,就像下面这个例子:
# Configure item pipelines # See http://scrapy.readthedocs.org/en/latest/topics/item-pipeline.html ITEM_PIPELINES = { #'mySpider.pipelines.SomePipeline': 300, "mySpider.pipelines.ItcastJsonPipeline":300 }
分配给每个类的整型值,确定了他们运行的顺序,item按数字从低到高的顺序,通过pipeline,通常将这些数字定义在0-1000范围内(0-1000随意设置,数值越低,组件的优先级越高)
4.4 重新启动爬虫
将parse()方法改为2.4.4中最后思考中的代码,然后执行下面的命令:
scrapy crawl itcast
查看当前目录是否生成teacher.json
5.Spider
Spider类定义了如何爬取某个(或某些)网站。包括了爬取的动作(例如:是否跟进链接)以及如何从网页的内容中提取结构化数据(爬取item)。 换句话说,Spider就是您定义爬取的动作及分析某个网页(或者是有些网页)的地方。
class scrapy.Spider
是最基本的类,所有编写的爬虫必须继承这个类。
主要用到的函数及调用顺序为:
__init__()
: 初始化爬虫名字和start_urls列表start_requests()
调用
make_requests_from url()
:生成Requests对象交给Scrapy下载并返回responseparse()
: 解析response,并返回Item或Requests(需指定回调函数)。Item传给Item pipline持久化 , 而Requests交由Scrapy下载,并由指定的回调函数处理(默认parse()),一直进行循环,直到处理完所有的数据为止。
5.1 源码参考
#所有爬虫的基类,用户定义的爬虫必须从这个类继承 class Spider(object_ref): #定义spider名字的字符串(string)。spider的名字定义了Scrapy如何定位(并初始化)spider,所以其必须是唯一的。 #name是spider最重要的属性,而且是必须的。 #一般做法是以该网站(domain)(加或不加 后缀 )来命名spider。 例如,如果spider爬取 mywebsite.com ,该spider通常会被命名为 mywebsite name = None #初始化,提取爬虫名字,start_ruls def __init__(self, name=None, **kwargs): if name is not None: self.name = name # 如果爬虫没有名字,中断后续操作则报错 elif not getattr(self, 'name', None): raise ValueError("%s must have a name" % type(self).__name__) # python 对象或类型通过内置成员__dict__来存储成员信息 self.__dict__.update(kwargs) #URL列表。当没有指定的URL时,spider将从该列表中开始进行爬取。 因此,第一个被获取到的页面的URL将是该列表之一。 后续的URL将会从获取到的数据中提取。 if not hasattr(self, 'start_urls'): self.start_urls = [] # 打印Scrapy执行后的log信息 def log(self, message, level=log.DEBUG, **kw): log.msg(message, spider=self, level=level, **kw) # 判断对象object的属性是否存在,不存在做断言处理 def set_crawler(self, crawler): assert not hasattr(self, '_crawler'), "Spider already bounded to %s" % crawler self._crawler = crawler @property def crawler(self): assert hasattr(self, '_crawler'), "Spider not bounded to any crawler" return self._crawler @property def settings(self): return self.crawler.settings #该方法将读取start_urls内的地址,并为每一个地址生成一个Request对象,交给Scrapy下载并返回Response #该方法仅调用一次 def start_requests(self): for url in self.start_urls: yield self.make_requests_from_url(url) #start_requests()中调用,实际生成Request的函数。 #Request对象默认的回调函数为parse(),提交的方式为get def make_requests_from_url(self, url): return Request(url, dont_filter=True) #默认的Request对象回调函数,处理返回的response。 #生成Item或者Request对象。用户必须实现这个类 def parse(self, response): raise NotImplementedError @classmethod def handles_request(cls, request): return url_is_from_spider(request.url, cls) def __str__(self): return "<%s %r at 0x%0x>" % (type(self).__name__, self.name, id(self)) __repr__ = __str__
5.2主要属性和方法
- name:定义spider名字的字符串。例如,如果spider爬取 mywebsite.com ,该spider通常会被命名为 mywebsite
- allowed_domains:包含了spider允许爬取的域名(domain)的列表,可选。
- start_urls:初始URL元祖/列表。当没有制定特定的URL时,spider将从该列表中开始进行爬取。
- start_requests(self):该方法必须返回一个可迭代对象(iterable)。该对象包含了spider用于爬取(默认实现是使用 start_urls 的url)的第一个Request。
当spider启动爬取并且未指定start_urls时,该方法被调用。
- parse(self, response):当请求url返回网页没有指定回调函数时,默认的Request对象回调函数。用来处理网页返回的response,以及生成Item或者Request对象。
- log(self, message[, level, component]):使用 scrapy.log.msg() 方法记录(log)message。 更多数据请参见 logging
5.3parse()方法的工作机制:
因为使用的
yield,而不是
return。
parse
函数将会被当做一个生成器使用。
scrapy
会逐一获取
parse
方法中生成的结果,并判断该结果是一个什么样的类型;
如果是
request
则加入爬取队列,如果是
item
类型则使用
pipeline
处理,其他类型则返回错误信息。
scrapy
取到第一部分的
request
不会立马就去发送这个
request
,只是把这个
request
放到队列里,然后接着从生成器里获取;
取尽第一部分的
request
,然后再获取第二部分的
item
,取到
item
了,就会放到对应的
pipeline
里处理;
parse()
方法作为回调函数
(callback)
赋值给了
Request
,指定
parse()
方法来处理这些请求
scrapy.Request(url, callback=self.parse)
Request
对象经过调度,执行生成
scrapy.http.response()
的响应对象,并送回给
parse()
方法,直到调度器中没有
Request
(递归的思路)
取尽之后,
parse()
工作结束,引擎再根据队列和
pipelines
中的内容去执行相应的操作;
程序在取得各个页面的
items
前,会先处理完之前所有的
request
队列里的请求,然后再提取
items
。
这一切的一切,
Scrapy
引擎和调度器将负责到底。
6.CrawlSpiders类
通过下面的命令可以快速创建CrawlSpider类的模板
scrapy genspider -t crawl tencent tencent.com
class scrapy.spiders.CrawlSpider它是Spider的派生类,Spider类的设计原则是只爬取start_url列表中的网页,而CrawlSpider类定义了一些规则(rule)来提供跟进link的方便的机制,从爬取的网页中获取link并继续爬取的工作更适合。
6.1源码参考
class CrawlSpider(Spider): rules = () def __init__(self, *a, **kw): super(CrawlSpider, self).__init__(*a, **kw) self._compile_rules() #首先调用parse()来处理start_urls中返回的response对象 #parse()则将这些response对象传递给了_parse_response()函数处理,并设置回调函数为parse_start_url() #设置了跟进标志位True #parse将返回item和跟进了的Request对象 def parse(self, response): return self._parse_response(response, self.parse_start_url, cb_kwargs={}, follow=True) #处理start_url中返回的response,需要重写 def parse_start_url(self, response): return [] def process_results(self, response, results): return results #从response中抽取符合任一用户定义'规则'的链接,并构造成Resquest对象返回 def _requests_to_follow(self, response): if not isinstance(response, HtmlResponse): return seen = set() #抽取之内的所有链接,只要通过任意一个'规则',即表示合法 for n, rule in enumerate(self._rules): links = [l for l in rule.link_extractor.extract_links(response) if l not in seen] #使用用户指定的process_links处理每个连接 if links and rule.process_links: links = rule.process_links(links) #将链接加入seen集合,为每个链接生成Request对象,并设置回调函数为_repsonse_downloaded() for link in links: seen.add(link) #构造Request对象,并将Rule规则中定义的回调函数作为这个Request对象的回调函数 r = Request(url=link.url, callback=self._response_downloaded) r.meta.update(rule=n, link_text=link.text) #对每个Request调用process_request()函数。该函数默认为indentify,即不做任何处理,直接返回该Request. yield rule.process_request(r) #处理通过rule提取出的连接,并返回item以及request def _response_downloaded(self, response): rule = self._rules[response.meta['rule']] return self._parse_response(response, rule.callback, rule.cb_kwargs, rule.follow) #解析response对象,会用callback解析处理他,并返回request或Item对象 def _parse_response(self, response, callback, cb_kwargs, follow=True): #首先判断是否设置了回调函数。(该回调函数可能是rule中的解析函数,也可能是 parse_start_url函数) #如果设置了回调函数(parse_start_url()),那么首先用parse_start_url()处理response对象, #然后再交给process_results处理。返回cb_res的一个列表 if callback: #如果是parse调用的,则会解析成Request对象 #如果是rule callback,则会解析成Item cb_res = callback(response, **cb_kwargs) or () cb_res = self.process_results(response, cb_res) for requests_or_item in iterate_spider_output(cb_res): yield requests_or_item #如果需要跟进,那么使用定义的Rule规则提取并返回这些Request对象 if follow and self._follow_links: #返回每个Request对象 for request_or_item in self._requests_to_follow(response): yield request_or_item def _compile_rules(self): def get_method(method): if callable(method): return method elif isinstance(method, basestring): return getattr(self, method, None) self._rules = [copy.copy(r) for r in self.rules] for rule in self._rules: rule.callback = get_method(rule.callback) rule.process_links = get_method(rule.process_links) rule.process_request = get_method(rule.process_request) def set_crawler(self, crawler): super(CrawlSpider, self).set_crawler(crawler) self._follow_links = crawler.settings.getbool('CRAWLSPIDER_FOLLOW_LINKS', True) CrawlSpider继承于Spider类,除了继承过来的属性外(name、allow_domains),还提供了新的属性和方法:
6.2 LinkExtractors
class scrapy.linkextractors.LinkExtractor
Link Extractors 的目的很简单: 提取链接。
每个LinkExtractor有唯一的公共方法是 extract_links(),它接收一个 Response 对象,并返回一个 scrapy.link.Link 对象。
Link Extractors要实例化一次,并且 extract_links 方法会根据不同的 response 调用多次提取链接。
class scrapy.linkextractors.LinkExtractor( allow = (), deny = (), allow_domains = (), deny_domains = (), deny_extensions = None, restrict_xpaths = (), tags = ('a','area'), attrs = ('href'), canonicalize = True, unique = True, process_value = None ) 主要参数: allow:满足括号中“正则表达式”的值会被提取,如果为空,则全部匹配。 deny:与这个正则表达式(或正则表达式列表)不匹配的URL一定不提取。 allow_domains:会被提取的链接的domains。 deny_domains:一定不会被提取链接的domains。 restrict_xpaths:使用xpath表达式,和allow共同作用过滤链接。
6.3 rules
在rules中包含一个或多个Rule对象,每个Rule对爬取网站的动作定义了特定操作。如果多个rule匹配了相同的链接,则根据规则在本集合中被定义的顺序,第一个会被使用。
class scrapy.spiders.Rule( link_extractor, callback = None, cb_kwargs = None, follow = None, process_links = None, process_request = None ) # 注意:当编写爬虫规则时,避免使用parse作为回调函数。由于CrawlSpider使用parse方法来实现其逻辑,如果覆盖了 parse方法,crawl spider将会运行失败。
link_extractor:是一个Link Extractor对象,用于定义需要提取的链接。 callback: 从link_extractor中每获取到链接时,参数所指定的值作为回调函数,该回调函数接受一个response作为其第一个参数。 follow:是一个布尔(boolean)值,指定了根据该规则从response提取的链接是否需要跟进。 如果callback为None,follow 默认设置为True ,否则默认为False。 process_links:指定该spider中哪个的函数将会被调用,从link_extractor中获取到链接列表时将会调用该函数。该方法主要用来过滤。 process_request:指定该spider中哪个的函数将会被调用, 该规则提取到每个request时都会调用该函数。 (用来过滤request)
6.4 CrawlSpider 版本
#tencent.py import scrapy from scrapy.spiders import CrawlSpider, Rule from scrapy.linkextractors import LinkExtractor from mySpider.items import TencentItem class TencentSpider(CrawlSpider): name = "tencent" allowed_domains = ["hr.tencent.com"] start_urls = [ "http://hr.tencent.com/position.php?&start=0#a" ] page_lx = LinkExtractor(allow=("start=\d+")) rules = [ Rule(page_lx, callback = "parseContent", follow = True) ] def parseContent(self, response): for each in response.xpath('//*[@class="even"]'): name = each.xpath('./td[1]/a/text()').extract()[0] detailLink = each.xpath('./td[1]/a/@href').extract()[0] positionInfo = each.xpath('./td[2]/text()').extract()[0] peopleNumber = each.xpath('./td[3]/text()').extract()[0] workLocation = each.xpath('./td[4]/text()').extract()[0] publishTime = each.xpath('./td[5]/text()').extract()[0] #print name, detailLink, catalog,recruitNumber,workLocation,publishTime item = TencentItem() item['name']=name.encode('utf-8') item['detailLink']=detailLink.encode('utf-8') item['positionInfo']=positionInfo.encode('utf-8') item['peopleNumber']=peopleNumber.encode('utf-8') item['workLocation']=workLocation.encode('utf-8') item['publishTime']=publishTime.encode('utf-8') yield item # parse() 方法不需要写 # def parse(self, response): # pass 运行: scrapy crawl tencent
7.Logging
7.1Scrapy提供了log功能,可以通过 logging 模块使用。
- 可以修改配置文件settings.py,任意位置添加下面两行,效果会清爽很多。
LOG_FILE = "TencentSpider.log" LOG_LEVEL = "INFO"
7.2 Log levels
- Scrapy提供5层logging级别:
- CRITICAL - 严重错误(critical)
- ERROR - 一般错误(regular errors)
- WARNING - 警告信息(warning messages)
- INFO - 一般信息(informational messages)
- DEBUG - 调试信息(debugging messages)
7.3 logging设置
通过在setting.py中进行以下设置可以被用来配置logging:
LOG_ENABLED
默认: True,启用loggingLOG_ENCODING
默认: 'utf-8',logging使用的编码LOG_FILE
默认: None,在当前目录里创建logging输出文件的文件名LOG_LEVEL
默认: 'DEBUG',log的最低级别LOG_STDOUT
默认: False 如果为 True,进程所有的标准输出(及错误)将会被重定向到log中。例如,执行 print "hello" ,其将会在Scrapy log中显示。
8.Request
常用的参数:
url: 就是需要请求,并进行下一步处理的url callback: 指定该请求返回的Response,由那个函数来处理。 method: 请求一般不需要指定,默认GET方法,可设置为"GET", "POST", "PUT"等,且保证字符串大写 headers: 请求时,包含的头文件。一般不需要。内容一般如下: # 自己写过爬虫的肯定知道 Host: media.readthedocs.org User-Agent: Mozilla/5.0 (Windows NT 6.2; WOW64; rv:33.0) Gecko/20100101 Firefox/33.0 Accept: text/css,*/*;q=0.1 Accept-Language: zh-cn,zh;q=0.8,en-us;q=0.5,en;q=0.3 Accept-Encoding: gzip, deflate Referer: http://scrapy-chs.readthedocs.org/zh_CN/0.24/ Cookie: _ga=GA1.2.1612165614.1415584110; Connection: keep-alive If-Modified-Since: Mon, 25 Aug 2014 21:59:35 GMT Cache-Control: max-age=0 meta: 比较常用,在不同的请求之间传递数据使用的。字典dict型 request_with_cookies = Request( url="http://www.example.com", cookies={'currency': 'USD', 'country': 'UY'}, meta={'dont_merge_cookies': True} ) encoding: 使用默认的 'utf-8' 就行。 dont_filter: 表明该请求不由调度器过滤。这是当你想使用多次执行相同的请求,忽略重复的过滤器。默认为False。 errback: 指定错误处理函数
9.Response
大部分参数和上面的差不多:
status: 响应码
_set_body(body): 响应体
_set_url(url):响应url
self.request = request
10.发送POST请求
- 可以使用
yield scrapy.FormRequest(url, formdata, callback)
方法发送POST请求。 - 如果希望程序执行一开始就发送POST请求,可以重写Spider类的
start_requests(self)
方法,并且不再调用start_urls里的url。
class mySpider(scrapy.Spider): # start_urls = ["http://www.example.com/"] def start_requests(self): url = 'http://www.renren.com/PLogin.do' # FormRequest 是Scrapy发送POST请求的方法 yield scrapy.FormRequest( url = url, formdata = {"email" : "mr_mao_hacker@163.com", "password" : "axxxxxxxe"}, callback = self.parse_page )
def parse_page(self, response): # do something
11.模拟登陆
使用FormRequest.from_response()方法模拟用户登录
通常网站通过 实现对某些表单字段(如数据或是登录界面中的认证令牌等)的预填充。
使用Scrapy抓取网页时,如果想要预填充或重写像用户名、用户密码这些表单字段, 可以使用 FormRequest.from_response() 方法实现。
下面是使用这种方法的爬虫例子:
import scrapy class LoginSpider(scrapy.Spider): name = 'example.com' start_urls = ['http://www.example.com/users/login.php'] def parse(self, response): return scrapy.FormRequest.from_response( response, formdata={'username': 'john', 'password': 'secret'}, callback=self.after_login ) def after_login(self, response): # check login succeed before going on if "authentication failed" in response.body: self.log("Login failed", level=log.ERROR) return # continue scraping with authenticated session...
12.Settings
Scrapy设置(settings)提供了定制Scrapy组件的方法。可以控制包括核心(core),插件(extension),pipeline及spider组件。比如 设置Json Pipeliine、LOG_LEVEL等。
参考文档:http://scrapy-chs.readthedocs.io/zh_CN/1.0/topics/settings.html#topics-settings-ref
内置设置参考手册
1.BOT_NAME 默认: 'scrapybot' 使用 startproject 命令创建项目时其也被自动赋值。 2.CONCURRENT_ITEMS 默认: 100 Item Processor(即 Item Pipeline) 同时处理(每个response的)item的最大值。 3.CONCURRENT_REQUESTS 默认: 16 Scrapy downloader 并发请求(concurrent requests)的最大值。 4.DEFAULT_REQUEST_HEADERS 默认: 如下 { 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'Accept-Language': 'en', } Scrapy HTTP Request使用的默认header。 5.DEPTH_LIMIT 默认: 0 爬取网站最大允许的深度(depth)值。如果为0,则没有限制。 6.DOWNLOAD_DELAY 默认: 0 下载器在下载同一个网站下一个页面前需要等待的时间。该选项可以用来限制爬取速度, 减轻服务器压力。同时也支持小数: DOWNLOAD_DELAY = 0.25 # 250 ms of delay 默认情况下,Scrapy在两个请求间不等待一个固定的值, 而是使用0.5到1.5之间的一个随机值 * DOWNLOAD_DELAY 的结果作为等待间隔。 7.DOWNLOAD_TIMEOUT 默认: 180 下载器超时时间(单位: 秒)。 8.ITEM_PIPELINES 默认: {} 保存项目中启用的pipeline及其顺序的字典。该字典默认为空,值(value)任意,不过值(value)习惯设置在0-1000范围内,值越小优先级越高。 ITEM_PIPELINES = { 'mySpider.pipelines.SomethingPipeline': 300, 'mySpider.pipelines.ItcastJsonPipeline': 800, } 9.LOG_ENABLED 默认: True 是否启用logging。 10.LOG_ENCODING 默认: 'utf-8' logging使用的编码。 11.LOG_LEVEL 默认: 'DEBUG' log的最低级别。可选的级别有: CRITICAL、 ERROR、WARNING、INFO、DEBUG 。 12.USER_AGENT 默认: "Scrapy/VERSION (+http://scrapy.org)" 爬取的默认User-Agent,除非被覆盖。 13.PROXIES: 代理设置 示例: PROXIES = [ {'ip_port': '111.11.228.75:80', 'password': ''}, {'ip_port': '120.198.243.22:80', 'password': ''}, {'ip_port': '111.8.60.9:8123', 'password': ''}, {'ip_port': '101.71.27.120:80', 'password': ''}, {'ip_port': '122.96.59.104:80', 'password': ''}, {'ip_port': '122.224.249.122:8088', 'password':''}, ] 14.COOKIES_ENABLED = False 禁用Cookies
15.DOWNLOADER_MIDDLEWARES
下载中间件
DOWNLOADER_MIDDLEWARES = {
'Douban250.middlewares.Douban250DownloaderMiddleware': 543,
}
13.middlewares
13.1反反爬虫
通常防止爬虫被反主要有以下几个策略:
- 动态设置User-Agent(随机切换User-Agent,模拟不同用户的浏览器信息)
- 禁用Cookies(也就是不启用cookies middleware,不向Server发送cookies,有些网站通过cookie的使用发现爬虫行为)
- 可以通过
COOKIES_ENABLED
控制 CookiesMiddleware 开启或关闭 - 设置延迟下载(防止访问过于频繁,设置为 2秒 或更高)
- Google Cache 和 Baidu Cache:如果可能的话,使用谷歌/百度等搜索引擎服务器页面缓存获取页面数据。
- 使用IP地址池:VPN和代理IP,现在大部分网站都是根据IP来ban的。
- 使用 Crawlera(专用于爬虫的代理组件),正确配置和设置下载中间件后,项目所有的request都是通过crawlera发出。
DOWNLOADER_MIDDLEWARES = { 'scrapy_crawlera.CrawleraMiddleware': 600 } CRAWLERA_ENABLED = True CRAWLERA_USER = '注册/购买的UserKey' CRAWLERA_PASS = '注册/购买的Password'
13.2 设置下载中间件(Downloader Middlewares)
下载中间件是处于引擎(crawler.engine)和下载器(crawler.engine.download())之间的一层组件,可以有多个下载中间件被加载运行。
- 当引擎传递请求给下载器的过程中,下载中间件可以对请求进行处理 (例如增加http header信息,增加proxy信息等);
- 在下载器完成http请求,传递响应给引擎的过程中, 下载中间件可以对响应进行处理(例如进行gzip的解压等)
要激活下载器中间件组件,将其加入到 DOWNLOADER_MIDDLEWARES 设置中。 该设置是一个字典(dict),键为中间件类的路径,值为其中间件的顺序(order)。
这里是一个例子:
DOWNLOADER_MIDDLEWARES = { 'mySpider.middlewares.cccc': 543, }
编写下载器中间件十分简单。每个中间件组件是一个定义了以下一个或多个方法的Python类:
class scrapy.contrib.downloadermiddleware.DownloaderMiddleware
13.3 process_request(self, request, spider)
- 当每个request通过下载中间件时,该方法被调用。
- process_request() 必须返回以下其中之一:一个 None 、一个 Response 对象、一个 Request 对象或 raise IgnoreRequest:
- 如果其返回 None ,Scrapy将继续处理该request,执行其他的中间件的相应方法,直到合适的下载器处理函数(download handler)被调用, 该request被执行(其response被下载)。
- 如果其返回 Response 对象,Scrapy将不会调用 任何 其他的 process_request() 或 process_exception() 方法,或相应地下载函数; 其将返回该response。 已安装的中间件的 process_response() 方法则会在每个response返回时被调用。
- 如果其返回 Request 对象,Scrapy则停止调用 process_request方法并重新调度返回的request。当新返回的request被执行后, 相应地中间件链将会根据下载的response被调用。
- 如果其raise一个 IgnoreRequest 异常,则安装的下载中间件的 process_exception() 方法会被调用。如果没有任何一个方法处理该异常, 则request的errback(Request.errback)方法会被调用。如果没有代码处理抛出的异常, 则该异常被忽略且不记录(不同于其他异常那样)。
- 参数:
request (Request
对象
)
– 处理的requestspider (Spider
对象
)
– 该request对应的spider
13.4 process_response(self, request, response, spider)
当下载器完成http请求,传递响应给引擎的时候调用
- process_request() 必须返回以下其中之一: 返回一个 Response 对象、 返回一个 Request 对象或raise一个 IgnoreRequest 异常。
- 如果其返回一个 Response (可以与传入的response相同,也可以是全新的对象), 该response会被在链中的其他中间件的 process_response() 方法处理。
- 如果其返回一个 Request 对象,则中间件链停止, 返回的request会被重新调度下载。处理类似于 process_request() 返回request所做的那样。
- 如果其抛出一个 IgnoreRequest 异常,则调用request的errback(Request.errback)。 如果没有代码处理抛出的异常,则该异常被忽略且不记录(不同于其他异常那样)。
- 参数:
request (Request
对象
)
– response所对应的requestresponse (Response
对象
)
– 被处理的responsespider (Spider
对象
)
– response所对应的spider
13.5 使用案例:
1. 创建middlewares.py文件。 Scrapy代理IP、Uesr-Agent的切换都是通过DOWNLOADER_MIDDLEWARES进行控制,我们在settings.py同级目录下创建middlewares.py文件,包装所有请求。 # middlewares.py #!/usr/bin/env python # -*- coding:utf-8 -*- import random import base64 from settings import USER_AGENTS from settings import PROXIES # 随机的User-Agent class RandomUserAgent(object): def process_request(self, request, spider): useragent = random.choice(USER_AGENTS) request.headers.setdefault("User-Agent", useragent) class RandomProxy(object): def process_request(self, request, spider): proxy = random.choice(PROXIES) if proxy['user_passwd'] is None: # 没有代理账户验证的代理使用方式 request.meta['proxy'] = "http://" + proxy['ip_port'] else: # 对账户密码进行base64编码转换 base64_userpasswd = base64.b64encode(proxy['user_passwd']) # 对应到代理服务器的信令格式里 request.headers['Proxy-Authorization'] = 'Basic ' + base64_userpasswd request.meta['proxy'] = "http://" + proxy['ip_port'] 为什么HTTP代理要使用base64编码: HTTP代理的原理很简单,就是通过HTTP协议与代理服务器建立连接,协议信令中包含要连接到的远程主机的IP和端口号,如果有需要身份验证的话还需要加上授权信息,服务器收到信令后首先进行身份验证,通过后便与远程主机建立连接,连接成功之后会返回给客户端200,表示验证通过,就这么简单,下面是具体的信令格式: CONNECT 59.64.128.198:21 HTTP/1.1 Host: 59.64.128.198:21 Proxy-Authorization: Basic bGV2I1TU5OTIz User-Agent: OpenFetion 其中Proxy-Authorization是身份验证信息,Basic后面的字符串是用户名和密码组合后进行base64编码的结果,也就是对username:password进行base64编码。 HTTP/1.0 200 Connection established OK,客户端收到收面的信令后表示成功建立连接,接下来要发送给远程主机的数据就可以发送给代理服务器了,代理服务器建立连接后会在根据IP地址和端口号对应的连接放入缓存,收到信令后再根据IP地址和端口号从缓存中找到对应的连接,将数据通过该连接转发出去。 2. 修改settings.py配置USER_AGENTS和PROXIES 添加USER_AGENTS: 添加代理IP设置PROXIES: USER_AGENTS = [ "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 2.0.50727; Media Center PC 6.0)", "Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 1.0.3705; .NET CLR 1.1.4322)", "Mozilla/4.0 (compatible; MSIE 7.0b; Windows NT 5.2; .NET CLR 1.1.4322; .NET CLR 2.0.50727; InfoPath.2; .NET CLR 3.0.04506.30)", "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN) AppleWebKit/523.15 (KHTML, like Gecko, Safari/419.3) Arora/0.3 (Change: 287 c9dfb30)", "Mozilla/5.0 (X11; U; Linux; en-US) AppleWebKit/527+ (KHTML, like Gecko, Safari/419.3) Arora/0.6", "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.2pre) Gecko/20070215 K-Ninja/2.1.1", "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9) Gecko/20080705 Firefox/3.0 Kapiko/3.0", "Mozilla/5.0 (X11; Linux i686; U;) Gecko/20070322 Kazehakase/0.4.5" ] 免费代理IP可以网上搜索,或者付费购买一批可用的私密代理IP: PROXIES = [ {'ip_port': '111.8.60.9:8123', 'user_passwd': 'user1:pass1'}, {'ip_port': '101.71.27.120:80', 'user_passwd': 'user2:pass2'}, {'ip_port': '122.96.59.104:80', 'user_passwd': 'user3:pass3'}, {'ip_port': '122.224.249.122:8088', 'user_passwd': 'user4:pass4'}, ] 除非特殊需要,禁用cookies,防止某些网站根据Cookie来封锁爬虫。 设置下载延迟 最后设置setting.py里的DOWNLOADER_MIDDLEWARES,添加自己编写的下载中间件类。 COOKIES_ENABLED = False DOWNLOAD_DELAY = 3 DOWNLOADER_MIDDLEWARES = { #'mySpider.middlewares.MyCustomDownloaderMiddleware': 543, 'mySpider.middlewares.RandomUserAgent': 1, 'mySpider.middlewares.ProxyMiddleware': 100 }