Scrapy抓取网页教程

scrapy是大名鼎鼎的Python抓取库,对抓取做了非常好的抽象,二次开发和使用都非常方便。要想使用它,首先要明白它的设计思路和框架。

scrapy框架结构

参考scrapy的Architecture Overview ,要想使用scrapy,至少要编写Spider,用于解析和获取要抓取的内容,抓取后的结构化结果使用Item来存储,Item会经过Item Pipelines便于做后期分析和处理。特殊情况下,可能要使用DownloaderMiddlewares来对请求做预处理。

scrapy data flow

创建项目

如果还没有安装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