Scrappy入门:百度贴吧图片爬虫

博客地址:Scrappy入门:百度贴吧图片爬虫html

Scrapy是Python很是有名的爬虫框架,框架自己已经为爬虫性能作了不少优化:多线程、整合xpath和图片专用管道等等,开发人员只要专一在功能需求上。git

基本Scrapy使用教程参考:初窥ScrapyScrapy入门教程github

学习一种技术或者一个框架最好的方式固然是用它作一些小工程,入门第一步我先选择了百度贴吧图片爬虫,由于既够简单又比较实用。数据库

由于此次涉及到图片的下载,而Scrapy自己为此提供了特殊的图片管道,因此果断直接用Scrapy的图片管道来帮助完成。Scrapy中管道的定义以下:数组

当Item在Spider中被收集以后,它将会被传递到Item Pipeline,一些组件会按照必定的顺序执行对Item的处理。
每一个item pipeline组件(有时称之为“Item Pipeline”)是实现了简单方法的Python类。他们接收到Item并经过它执行一些行为,同时也决定此Item是否继续经过pipeline,或是被丢弃而再也不进行处理。多线程

对于管道的典型应用场景以下:app

清理HTML数据
验证爬取的数据(检查item包含某些字段)
查重(并丢弃)
将爬取结果保存到数据库中框架

Scrappy图片管道的使用教程参考:下载项目图片异步

使用Scrapy最重要的就是编写特定的spider类,本文指定的spider类是BaiduTieBaSpider,来看下它的定义:scrapy

import scrapy
import requests
import os
from tutorial.items import TutorialItem
class BaiduTieBaSpider(scrapy.spiders.Spider):
    name = 'baidutieba'
    start_urls = ['http://tieba.baidu.com/p/2235516502?see_lz=1&pn=%d' % i for i in range(1, 38)]
    image_names = {}
    def parse(self, response):
        item = TutorialItem()
        item['image_urls'] = response.xpath("//img[@class='BDE_Image']/@src").extract()
        for index, value in enumerate(item['image_urls']):
            number = self.start_urls.index(response.url) * len(item['image_urls']) + index
            self.image_names[value] = 'full/%04d.jpg' % number
        yield item

这里要关注Scrappy作的两件事情:

  • 根据start_urls 中的URL地址访问页面并获得返回

  • parse(self, response)函数就是抓取到页面以后的解析工做

那么首先就是start_urls的构造,这里是观察了百度贴吧里的URL规则,其中see_lz=1表示只看楼主,pn=1表示第一页,根据这些规则得出了一个URL数组。而后再观察单个页面的HTML源码,得出每一个楼层发布的图片对应的img标签的类为BDE_Image,这样就能够得出xpath的表达式:xpath("//img[@class='BDE_Image']/@src"),来提取楼层中全部图片的src,赋值到item对象的image_urls字段中,当spider返回时,item会进入图片管道进行处理(即Scrapy会自自动帮你下载图片)。

对应的item类的编写和setting.py文件的修改详见上文的教程。

到这里下载图片的基本功能都完成了,可是有个问题:我想要按顺序保存图片怎么办?

形成这个问题的关键就是Scrapy是多线程抓取页面的,也就是对于start_urls中地址的抓取都是异步请求,以及item返回以后到图片管道后对每张图片的URL也是异步请求,因此是没法保证每张图片返回的顺序的。

那么这个问题怎么解决呢?试了几种办法以后,获得一个相对理想的解决方案就是:制做一个字典,key是图片地址,value是对应的编号。因此就有了代码中的image_namesnumber = self.start_urls.index(response.url) * len(item['image_urls']) + index,而后再定制图片管道,定制的方法详见上文给出的教程连接,在本文中定制须要作的事情就是重写file_path函数,代码以下:

import scrapy
from scrapy.contrib.pipeline.images import ImagesPipeline
from scrapy.exceptions import DropItem
from tutorial.spiders.BaiduTieBa_spider import BaiduTieBaSpider

class MyImagesPipeline(ImagesPipeline):

    def file_path(self, request, response=None, info=None):
        image_name = BaiduTieBaSpider.image_names[request.url]
        return image_name

    def get_media_requests(self, item, info):
        for image_url in item['image_urls']:
            yield scrapy.Request(image_url)

    def item_completed(self, results, item, info):
        image_paths = [x['path'] for ok, x in results if ok]
        if not image_paths:
            raise DropItem("Item contains no images")
        item['image_paths'] = image_paths
        return item

file_path函数就是返回每张图片保存的路径,当咱们有一张完整的字典以后,只要根据request的URL去取相应的编号便可。

这个方法显然是比较消耗内存的,由于若是图片不少的话,须要维护的字典的条目也会不少,但从已经折腾过的几个解决方案(例如不用管道而采用手动阻塞的方式来下载图片)来看,它的效果是最好的,付出的代价也还算能够接受。

Scrappy入门第一个小demo就写到这里。

相关文章
相关标签/搜索