twisted介绍
Twisted是用Python实现的基于事件驱动的网络引擎框架,scrapy正是依赖于twisted,
从而基于事件循环机制实现爬虫的并发。
scrapy的pipeline文件和items文件
这两个文件有什么作用
先看看我们上篇的示例:
代码语言:javascript复制# -*- coding: utf-8 -*-
import scrapy
class ChoutiSpider(scrapy.Spider):
'''
爬去抽屉网的帖子信息
'''
name = 'chouti'
allowed_domains = ['chouti.com']
start_urls = ['http://chouti.com/']
def parse(self, response):
# 获取帖子列表的父级div
content_div = response.xpath('//div[@id="content-list"]')
# 获取帖子item的列表
items_list = content_div.xpath('.//div[@class="item"]')
# 打开一个文件句柄,目的是为了将获取的东西写入文件
with open('articles.log','a ',encoding='utf-8') as f:
# 循环item_list
for item in items_list:
# 获取每个item的第一个a标签的文本和url链接
text = item.xpath('.//a/text()').extract_first()
href = item.xpath('.//a/@href').extract_first()
# print(href, text.strip())
# print('-'*100)
f.write(href 'n')
f.write(text.strip() 'n')
f.write('-'*100 'n')
# 获取分页的页码,然后让程序循环爬去每个链接
# 页码标签对象列表
page_list = response.xpath('//div[@id="dig_lcpage"]')
# 循环列表
for page in page_list:
# 获取每个标签下的a标签的url,即每页的链接
page_a_url = page.xpath('.//a/@href').extract()
# 将域名和url拼接起来
page_url = 'https://dig.chouti.com' page_a_url
# 重要的一步!!!!
# 导入Request模块,然后实例化一个Request对象,然后yield它
# 就会自动执行Request对象的callback方法,爬去的是url参数中的链接
from scrapy.http import Request
yield Request(url=page_url,callback=self.parse)
在这个示例中,虽然我们已经通过chouti.py一个文件中的parse方法实现了爬去抽屉网的新闻并将之保存在文件中的功能,
但是我们会发现有两个问题:
1、在循环爬去每一页的时候,每次都需要重新打开然后再关闭文件,如果数据量庞大的话,这对性能有很大的影响。
2、我们将解析和数据持久化都放在了同一个文件的同一个方法中,没有做到分工明确
如果要解决这两个问题,则需要用到scrapy自动为我们生成的pipeline文件和items文件
这两个文件怎么用
如果我们要使用这两个文件从而解决问题,则需要有四部操作:
a.编写pipeline文件中的类,格式如下:
代码语言:javascript复制class XXXPipeline(object):
def process_item(self, item, spider):
return item
b.编写items文件中的类,格式如下:
代码语言:javascript复制class XXXItem(scrapy.Item):
href = scrapy.Field()
title = scrapy.Field()
c.配置settings文件
代码语言:javascript复制ITEM_PIPELINES = {
'xxx.pipelines.XXXPipeline': 300,
# 'xxx.pipelines.XXXPipeline2': 600, # 后面的数字为优先级,数字越大,优先级月底
}
d.在parse方法中yield一个Item对象
代码语言:javascript复制from xxx.items import XXXItem
def parse(self, response):
...
yield XXXItem(text=text,href=href)
执行流程为:
当我们在执行爬虫中的parse方法的时候,scrapy一旦解析到有yield XXXitem的语句,就会到配置文件中找
ITEM_PIPELINES的配置项,进而找到XXXPipeline类,然后执行其中的方法,我们就可以在方法中做很多操作
当然,pipeline中不止process_item一个方法。
Pipeline中的方法详解
代码语言:javascript复制class FilePipeline(object):
def __init__(self,path):
self.f = None
self.path = path
@classmethod
def from_crawler(cls, crawler):
"""
初始化时候,用于创建pipeline对象
:param crawler:
:return:
"""
# 从配置文件中获取配置好的文件存放目录
path = crawler.settings.get('HREF_FILE_PATH')
return cls(path)
def open_spider(self,spider):
"""
爬虫开始执行时,调用
:param spider:
:return:
"""
self.f = open(self.path,'a ')
def process_item(self, item, spider):
# 在这里做持久化
self.f.write(item['href'] 'n')
return item # 交给下一个pipeline的process_item方法
# raise DropItem()# 如果写上这一句,后续的 pipeline的process_item方法不再执行
def close_spider(self,spider):
"""
爬虫关闭时,被调用
:param spider:
:return:
"""
self.f.close()
去重
scrapy内部实现的去重
从上一篇的例子我们可以看出,其实scrapy内部在循环爬去页码的时候,已经帮我们做了去重功能的,
因为我们在首页可以看到1,2,3,4,5,6,7,8,9,10页的页码以及连接,当爬虫爬到第二页的时候,
还是可以看到这10个页面及连接,然后它并没有再重新把第一页爬一遍。
它内部实现去重的原理是,将已爬去的网址存入一个set集合里,每次爬取新页面的时候就先看一下是否在集合里面
如果在,就不再爬去,如果不在就爬取,然后再添加入到set里。当然,这个集合存放的不是原网址,
而是将链接通过request_fingerprint()方法将它变成一个类似于md5的值,这样可以节省存储空间
自定义去重
虽然scrapy已经帮我们实现了去重,但是有时候不足以满足我们的需求,这样就需要我们自定义去重了
自定义去重分两步
1、编写DupeFilter类
代码语言:javascript复制from scrapy.dupefilter import BaseDupeFilter
from scrapy.utils.request import request_fingerprint
class XXXDupeFilter(BaseDupeFilter):
def __init__(self):
'''初始化一个集合,用来存放爬去过的网址'''
self.visited_fd = set()
@classmethod
def from_settings(cls, settings):
'''
如果我们自定义了DupeFilter类并且重写了父类的该方法,
scrapy会首先执行该方法,获取DupeFilter对象,
如果没有定义,则会执行init方法来获取对象
'''
return cls()
def request_seen(self, request):
'''在此方法中做操作,判断以及添加网址到set里'''
# 将request里的url转换下,然后判断是否在set里
fd = request_fingerprint(request=request)
# 循环set集合,如果已经在集合里,则返回True,爬虫将不会继续爬取该网址
if fd in self.visited_fd:
return True
self.visited_fd.add(fd)
def open(self): # can return deferred
'''开始前执行此方法'''
print('开始')
def close(self, reason): # can return a deferred
'''结束后执行此方法'''
print('结束')
def log(self, request, spider): # log that a request has been filtered
'''在此方法中可以做日志操作'''
print('日志')
2.配置settings文件
代码语言:javascript复制# 修改默认的去重规则
# DUPEFILTER_CLASS = 'scrapy.dupefilter.RFPDupeFilter'
DUPEFILTER_CLASS = 'xxx.dupefilters.XXXDupeFilter'
深度
深度就是爬虫所要爬取的层级
限制深度只需要配置一下即可
代码语言:javascript复制# 限制深度
DEPTH_LIMIT = 3
cookie
获取上一次请求之后获得的cookie
代码语言:javascript复制from scrapy.http.cookies import CookieJar
class ChoutiSpider(scrapy.Spider):
name = 'chouti'
allowed_domains = ['chouti.com']
start_urls = ['https://dig.chouti.com/']
cookie_dict = {}
def parse(self, response):
# 去响应头中获取cookie,cookie保存在cookie_jar对象
cookie_jar = CookieJar()
cookie_jar.extract_cookies(response, response.request)
# 去对象中将cookie解析到字典
for k, v in cookie_jar._cookies.items():
for i, j in v.items():
for m, n in j.items():
self.cookie_dict[m] = n.value
再次请求的时候携带cookie
代码语言:javascript复制 yield Request(
url='https://dig.chouti.com/login',
method='POST',
body="phone=861300000000&password=12345678&oneMonth=1",#
cookies=self.cookie_dict,
headers={
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
},
callback=self.check_login
)