1.使用item pipeline处理数据
如果我们在爬取到数据之后想对数据进行一些处理再进行存储的话,我们可以使用pipline,pipeline在pipeline.py中进行定义,例如:
class PriceConverterPipeline(object):
exchange_rate = 8.5309
def process_item(self, item, spider):
price = float(item['price'][1:]) * self.exchange_rate
item['price'] = '¥%.2f' % price
return item
与其他地方不同的是,pipline并不需要继承特定的基类,只需要实现某些特定的方法,例如process_item(必须实现)、open_spider:Spider打开时(处理数据前)回调该方法、close_spider:Spider关闭时(处理数据后)回调该方法,from_crawler:创建Item Pipeline对象时回调该类方法。
Item:爬取到的一项数据(Item或字典)
Spider:爬取此项数据的Spider对象。
第二步:启用itempipeline,光是我们自己写了代码其实还不够,需要我们在scrapy设置中启动它。我们需要在settings.py中配置,如下:
ITEM_PIPELINES = {
'example.pipelines.PriceConverterPipeline': 300,
}
我们把想要启用的Item Pipeline 添加到这个字典中,其中每一项的键是每一个Item Pipeline类的导入 路径,值是一个0~1000的数字,同时启用多个Item Pipeline时, Scrapy根据这些数值决定各Item Pipeline处理数据的先后次序,数值小的在前。最后形成一条数据处理的流水线。
2.更加优雅的提取链接
Scrapy提供了一个专门用于提取链接的类LinkExtractor,在提取 大量链接或提取规则比较复杂时,使用LinkExtractor更加方便。
from scrapy.linkextractors import LinkExtractor
le = LinkExtractor(restrict_css='ul.pager li.next')
links = le.extract_links(response)
创建一个LinkExtractor对象,使用一个或多个构造器参数描 述提取规则,这里传递给restrict_css参数一个CSS选择器表 达式。它描述出下一页链接所在的区域(在li.next下),调用LinkExtractor对象的extract_links方法传入一个 Response对象,该方法依据创建对象时所描述的提取规则,在 Response对象所包含的页面中提取链接,最终返回一个列表, 其中的每一个元素都是一个Link对象,即提取到的一个链接。
LinkExtractor所包含的各个参数,
allow:接收一个正则表达式或一个正则表达式列表,提取绝对url与 正则表达式匹配的链接,如果该参数为空(默认),就提取全 部链接。
deny:接收一个正则表达式或一个正则表达式列表,与allow相反, 排除绝对url与正则表达式匹配的链接。
allow_domains:接收一个域名或一个域名列表,提取到指定域的链接。
deny_domains:接收一个域名或一个域名列表,与allow_domains相反,排除 到指定域的链接。
restrict_xpaths:接收一个XPath表达式或一个XPath表达式列表,提取XPath表 达式选中区域下的链接。
restrict_css:接收一个CSS选择器或一个CSS选择器列表,提取CSS选择器选 中区域下的链接。
tags:接收一个标签(字符串)或一个标签列表,提取指定标签内的 链接,默认为['a', 'area']。
attrs:接收一个属性(字符串)或一个属性列表,提取指定属性内的 链接,默认为['href']。
process_value:接收一个形如func(value)的回调函数。如果传递了该参数, LinkExtractor将调用该回调函数对提取的每一个链接(如a的 href)进行处理,回调函数正常情况下应返回一个字符串(处 理结果),想要抛弃所处理的链接时,返回None。
3.自定义导出数据的格式
在Scrapy中,负责导出数据的组件被称为Exporter(导出器), Scrapy内部实现了多个Exporter,每个Exporter实现一种数据格式的导出。
目前支持的数据格式包括:JSON,CSV,XML,Pickle,Marshal
没有excel,也没有txt格式,这些需要我们自己去定义
scrapy crawl books -o books.csv
-o books.csv指定了导出文件的路径
scrapy crawl books -t csv -o books1.data
-t参数中的数据格式字符串(如 csv、json、xml)为键,在配置字典FEED_EXPORTERS中搜索 Exporter,如果我们添加了自己定义的格式,可以在settings中定义,
FEED_EXPORTERS = {'excel': 'my_project.my_exporters.ExcelItemExporter'}
FEED_URI 导出文件路径, FEED_FORMAT 导出数据格式, FEED_EXPORT_ENCODING 导出文件编码(默认情况下json文件使用数字编码,其他使用 utf-8编码),FEED_EXPORT_FIELDS 导出数据包含的字段(默认情况下导出所有字段) 并指定次序。
每一个Exporter都是BaseItemExporter的一个子类, BaseItemExporter定义了一些抽象接口待子类实现export_item(self, item) 负责导出爬取到的每一项数据,参数item为一项爬取到的数 据,每个子类必须实现该方法。 start_exporting(self) 在导出开始时被调用,可在该方法中执行某些初始化工作。finish_exporting(self) 在导出完成时被调用,可在该方法中执行某些清理工作。
我们想要自定义导出数据的格式,只需要完成export_item即可,如下:
from scrapy.exporters import BaseItemExporter
import xlwt
class ExcelItemExporter(BaseItemExporter):
def __init__(self, file, **kwargs):
self._configure(kwargs)
self.file = file
self.wbook = xlwt.Workbook()
self.wsheet = self.wbook.add_sheet('scrapy')
self.row = 0
def finish_exporting(self):
self.wbook.save(self.file)
def export_item(self, item):
fields = self._get_serialized_fields(item)
for col, v in enumerate(x for _, x in fields):
self.wsheet.write(self.row, col, v)
self.row += 1
这段代码需要我们自己创建一个py文件(与setting.py同级)并写在里面。
之后我们需要在setting.py中开启对它的调用,
FEED_EXPORTERS = {'excel': 'example.my_exporters.ExcelItemExporter'}
之后,成功运行!
scrapy crawl books -t excel -o books.xls
4.下载图片与文件
Scrapy框架内部提供了两个Item Pipeline,专门用于下载文件和 图片:FilesPipeline,ImagesPipeline
我们使用时只需要,通过item的一个特殊字段将要下载文件或图片的url传递给它 们,它们会自动将文件或图片下载到本地,并将下载结果信息存入item 的另一个特殊字段,以便用户在导出文件中查阅。
首先在setting.py中启用FilesPipeline
ITEM_PIPELINES = {'scrapy.pipelines.files.FilesPipeline': 1}
指定文件的下载目录
FILES_STORE = '/home/liushuo/Download/scrapy'
在Spider解析一个包含文件下载链接的页面时,将所有需要下载文件的url地址收集到一个列表,赋给item的file_urls字 段(item['file_urls'])。FilesPipeline在处理每一项item时, 会读取item['file_urls'],对其中每一个url进行下载。
class DownloadBookSpider(scrapy.Spider):
def parse(response):
item = {}
item['file_urls'] = []
for url in response.xpath('//a/@href').extract():
download_url = response.urljoin(url)
item['file_urls'].append(download_url)
yield item
当FilesPipeline下载完item['file_urls']中的所有文件后,会将各文 件的下载结果信息收集到另一个列表,赋给item的files字段 (item['files'])。下载结果信息包括以下内容: Path文件下载到本地的路径(相对于FILES_STORE的相对路 径)。 Checksum文件的校验和。 url文件的url地址。
ImagesPipeline的使用与FilesPipeline大同小异,
# settings.py
ITEM_PIPELINES = {
'scrapy.pipelines.images.ImagesPipeline': 1,
}
IMAGES_STORE = '/path/to/your/image/directory'
# myproject/spiders/my_spider.py
import scrapy
from myproject.items import MyImageItem
class MySpider(scrapy.Spider):
name = 'myspider'
start_urls = ['https://example.com']
def parse(self, response):
item = MyImageItem()
item['image_urls'] = response.css('img::attr(src)').extract()
yield item
5.爬虫的重头戏---如何破解反扒机制
一、模拟登录
登录是向服务器发送含有登录表单 数据的HTTP请求(通常是POST)。Scrapy提供了一个 FormRequest类(Request的子类),专门用于构造含有表单数据的 请求,FormRequest的构造器方法有一个formdata参数,接收字典形 式的表单数据。对于表单之中的隐藏信息,可以先使用爬虫获取,或者是采用form_response的方法让scrapy帮助我们去解析。
import scrapy
class MySpider(scrapy.Spider):
name = 'myspider'
start_urls = ['https://example.com']
def parse(self, response):
# 获取表单字段值
formdata = {
'username': 'myusername',
'password': 'mypassword',
}
# 创建 FormRequest
yield scrapy.FormRequest(
url='https://example.com/login',
formdata=formdata,
callback=self.parse_after_login
)
def parse_after_login(self, response):
# 处理登录后的逻辑
# ...
二、识别验证码
页面中的验证码图片对应一个元素,即一张图片,浏览器加 载完登录页面后,会携带之前访问获取的Cookie信息,继续发送一个 HTTP请求加载验证码图片。和账号密码输入框一样,验证码输入框也 对应一个元素,因此用户输入的验证码会成为表单数据的一部 分,表单提交后由网站服务器程序验证。
第一种破解方法,ocr识别,我们这里采用的是tesseract-ocr,安装包下载地址:Index of /tesseract,只需要下一步即可,之后我们需要在系统环境配置中去添加它的路径,这里暂时不做展开,之后我们需要pip install pillow pytesseract
之后便可以编写代码进行识别了,这种方法识别率并不是百分之一百,有失败的风险。
import cv2
import pytesseract
# 读取图像
original_image = cv2.imread('captcha.png')
# 将图像转换为灰度
grey = cv2.cvtColor(original_image, cv2.COLOR_RGB2GRAY)
# 使用 Pytesseract 识别文本
text = pytesseract.image_to_string(grey)
print("识别结果:", text)
在获取到了识别的结果候我们便可以将其返回给scrapy进行登录操作。
第二种方法:平台识别,即调用别人已经完成的识别验证码的服务
第三种方法:人工识别,检测到验证码,卡住程序,显示出验证码让人工来输入,输入结果之后继续下一步爬取
三、cookie登录
目前网站的验证 码越来越复杂,某些验证码已经复杂到人类难以识别的程度,有些时候 提交表单登录的路子难以走通。此时,我们可以换一种登录爬取的思 路,在使用浏览器登录网站后,包含用户身份信息的Cookie会被浏览 器保存在本地,如果Scrapy爬虫能直接使用浏览器中的Cookie发送 HTTP请求,就可以绕过提交表单登录的步骤。
获取浏览器的cookie,我们在这里采用的是browsercookie库来获取浏览器存储的cookie(本地),这里获取到的cookie都是我们自己的。
目前scrapy中有一个处理的cookie的中间件名为CookiesMiddleware,它会自动帮助我们处理cookie信息,但没法写入我们自己的cookie,因此我们需要重新写,
import browsercookie
from scrapy.downloadermiddlewares.cookies import CookiesMiddleware
class BrowserCookiesMiddleware(CookiesMiddleware):
def __init__(self, debug=False):
super().__init__(debug)
self.load_browser_cookies()
def load_browser_cookies(self):
# 加载Chrome 浏览器中的Cookie
jar = self.jars['chrome']
chrome_cookiejar = browsercookie.chrome()
for cookie in chrome_cookiejar:
jar.set_cookie(cookie)
# 加载Firefox 浏览器中的Cookie
jar = self.jars['firefox']
firefox_cookiejar = browsercookie.firefox()
for cookie in firefox_cookiejar:
jar.set_cookie(cookie)