scrapy是大名鼎鼎的Python抓取库,对抓取做了非常好的抽象,二次开发和使用都非常方便。要想使用它,首先要明白它的设计思路和框架。
scrapy框架结构
参考scrapy的Architecture Overview ,要想使用scrapy,至少要编写Spider,用于解析和获取要抓取的内容,抓取后的结构化结果使用Item来存储,Item会经过Item Pipelines便于做后期分析和处理。特殊情况下,可能要使用DownloaderMiddlewares来对请求做预处理。
创建项目
如果还没有安装scrapy,请执行:
pip install scrapy
在当前目录下创建一个抓取项目:
scrapy startproject As
cd As
scrapy genspider as as.com
As目录就已经初始化完成,在settings.py中调整一些基础配置:
BOT_NAME = 'As'
ROBOTSTXT_OBEY = False
LOG_FILE = '/tmp/as.scrapy.log'
编写spider
vi As/spiders/as.py
可以看到,主要需要编写parse方法:
import scrapy
class AsSpider(scrapy.Spider):
name = 'as'
allowed_domains = ['as.com']
start_urls = ['http://as.com/']
def parse(self, response):
for qt in response.css('ul.tag_panel i.tag a'):
yield scrapy.Request(qt.attrib['href'])
# 可以自定义初始化启动方法,否则scrapy会默认根据start_urls来遍历启动默认的parse方法
def start_requests(self):
for u in self.start_urls:
yield scrapy.Request(u, \
callback=self.parse, \
headers=self.headers, \
errback=self.errback)
# 错误处理回掉函数
def errback(self, failure):
self.logger.error(repr(failure))
编写Item
vi As/items.py
class AsItem(scrapy.Item):
qc = scrapy.Field()
qt = scrapy.Field()
qname = scrapy.Field()
就可以在spider中使用item来保存数据了:
vi As/spiders/as.py
from As.items import AsItem
def parse(self, response):
#......
item = AsItem(qc='qc')
item['qt'] = response.css('title::text').get()
# 这会启动item pipelines
yield item
#......
编写Item Pipeline
vi As/pipelines.py
import logging
class AsPipeline():
def __init__(self, *args, **kwargs):
pass
def process_item(self, item, spider):
logging.warn('>>>>> %s', str(item))
# TODO save item
# MUST return item for next pipeline
return item
在settings.py中配置,后面的数字表示优先级,数字越小越优先:
ITEM_PIPELINES = {
'scrapy.pipelines.files.FilesPipeline': 1,
'As.pipelines.AsPipeline': 300,
}
#如果要使用scrapy.pipelines.files.FilesPipeline下载文件,需要配置存储路径
FILES_STORE = '/tmp/asimg'
下载文件或图片
scrapy提供了scrapy.pipelines.files.FilesPipeline 和scrapy.pipelines.images.ImagesPipeline来协助下载文件,两者基本相同,ImagesPipeline提供了图片格式转换,thumb生成等更多功能。使用他们需要在Item中做如下定义:
class AsItem(scrapy.Item):
#...
# 用于FilesPipeline
file_urls = scrapy.Field()
files = scrapy.Field()
# 用于ImagesPipeline
image_urls = scrapy.Field()
images = scrapy.Field()
在Spider中把,相应的图片URL放入AsItem.file_urls或AsItem.image_urls即可完成下载:
def parse(self, response):
#......
item = AsItem(qc='qc')
item['qt'] = response.css('title::text').get()
item.file_urls = response.css('img::attr(src)').getall()
# 这会启动item pipelines
yield item
#......
AsItem中的属性files和images是下载完成之后的结果,字段类似如下:
'files': [{'checksum': 'c3416b34778f678b095f2df81e98f928',
'path': 'full/96dbc749e2fef1bcf5153a88c3f453241a45dc05.png',
'status': 'downloaded',
'url': 'http://as.com/images/140955_5b975c33c7b06.png'},
{'checksum': 'a343a8b7b30d36bb26e01748adee0c48',
'path': 'full/1c5f8e03a0983191bd7ffa24d1c8d47f240f9039.png',
'status': 'downloaded',
'url': 'http://as.com/images/140955_5b975c33e8f1c.png'}],
开始抓取
完成上述工作之后,一个crawler基本就完成了,可以执行:
scrapy crawl as -o res.jl
其中 -o 选项可以直接保存最终的item到文件中,jl是jsonline格式,scrapy还支持csv等多种格式。当然,你也可以在pipeline中自己编写保存的代码,保存到数据库等系统中。
crawler最复杂的莫过于测试网页结果提取是否正常,scrapy提供了shell可以交互测试:
scrapy shell https://www.baidu.com
>>> response.css('title').getall()
['<title>百度一下,你就知道</title>']
>>> response.css('img::attr(src)').getall()
['//www.baidu.com/img/bd_logo1.png', '//www.baidu.com/img/gs.gif']
>>>
示例程序
一个完整的更复杂的示例程序请参考https://github.com/uioch/codepop/tree/main/AsCrapy