基于Scrapy的百度图片爬虫

一、准备工作

1.1 激活虚拟环境

activate envname

1.2 新建Scrapy项目

scrapy startproject projectname

1.3 新建Spider

scrapy genspider images image.baidu.com

1.4 PyCharm打开项目

1.5 选择项目解释器

至此,可以开始编写爬虫程序了

二、网页分析

用Chrome打开百度图片,输入关键字“单色釉瓷”,右键检查元素


发现图片的URL是在标签main_img img-hover中,而在网页源代码中却查找不到


因为百度图片的网页是动态的,采用的是Ajax+JSON机制。网页原始数据是没有图片的,通过运行JavaScript,把图片数据插入到网页的HTML标签中。所以我们在开发者工具中虽然能看到这个HTML标签,但是网页的原始数据其实没有这个标签,它只在运行时加载和渲染。真实的图片信息被打包放在JSON文件当中,所以真正要解析的是JSON文件。

点击Network–XHR,在往下滑动滚动条时,会连续出现名为acjson?tn=resultjson_com&ipn=···的请求,点击其中一条,再点击Preview,我们看到这是一条JSON数据,点开data,我们看到这里面有30条数据,每一条都对应着一张图片。


说明百度图片一开始只加载30张图片,当往下滑动滚动条时,页面会动态加载1条JSON数据,每条JSON数据里面包含了30条信息,信息里面又包含了图片的URL,JavaScript会将这些URL解析并显示出来。这样,每次滚动就又加载了30张图片。

点击Headers,对比这些JSON数据的头部信息。发现Headers下的Query String Parameters中的字段大多保持不变,只有pn字段保持以30为步长递增。



点击其中一个JSON,在Headers下的General中就有一个Request URL,其内容就是JSON的URL


三、 代码编写

3.1 构造请求

settings.py中定义变量MAX_PAGE作为爬取页数,并将ROBOTSTXT_OBEY置为FALSE
images.py中定义start_requests()方法,用来生成350次请求

    def start_requests(self):
        data = {'queryWord': '单色釉瓷', 'word': '单色釉瓷'}
        base_url = 'https://image.baidu.com/search/acjson?tn=resultjson_com&ipn=rj&ct=201326592&is=&fp=result&queryWord='
        for page in range(1, self.settings.get('MAX_PAGE') + 1):
            data['pn'] = page * 30
            url = base_url + quote(data['queryWord']) + '&cl=2&lm=-1&ie=utf-8&oe=utf-8&adpicid=&st=-1&z=&ic=0&word=' + \
                  quote(data['word']) + '&s=&se=&tab=&width=&height=&face=0&istype=2&qc=&nc=1&fr=&expermode=&pn=' + \
                  quote(str(data['pn'])) + '&rn=30&gsm=' + str(hex(data['pn']))
            yield Request(url, self.parse)

运行爬虫,可看到链接都请求成功。
scrapy crawl images

3.2 提取信息

定义Item,叫作BaiduimagesItem

from scrapy import Item, Field


class BaiduimagesItem(Item):
    url = Field()
    pass

提取Spider里有关信息。改写parse()方法

    def parse(self, response):
        images = json.loads(response.body)['data']
        for image in images:
            item = BaiduimagesItem()
            try:
                item['url'] = image.get('thumbURL')
                yield item
            except Exception as e:
                print(e)
        pass

首先解析JSON,遍历其thumbURL字段,取出图片信息,再对BaiduimagesItem赋值,生成Item对象

3.3 存储信息

下载图片需要用到Scrapy提供的ImagesPipeline,首先定义存储文件的路径,需要定义一个IMAGES_STORE变量,在settings.py中添加:

IMAGES_STORE = './images'

内置的ImagesPipeline会默认读取Item的image_urls字段,并认为该字段是一个列表形式,它会遍历Item的image_urls字段,然后取出每个URL进行图片下载。
但是现在生成的Item的图片链接字段并不是image_urls表示的,也不是列表形式,而是单个的URL。所以为了实现下载,我们需要重新定义下载的部分逻辑,即要自定义ImagesPipeline,继承内置的ImagesPipeline,重写几个方法。

from scrapy import Request
from scrapy.exceptions import DropItem
from scrapy.pipelines.images import ImagesPipeline


class BaiduimagesPipeline(ImagesPipeline):

    def file_path(self, request, response=None, info=None):
        url = request.url
        file_name = url.split('/')[-1]
        return file_name

    def item_completed(self, results, item, info):
        image_paths = [x['path'] for ok, x in results if ok]
        if not image_paths:
            raise DropItem('Image Downloaded Failed')
        return item

    def get_media_requests(self, item, info):
        yield Request(item['url'])

get_media_requests(),第一个参数item是爬取生成的Item对象。我们将它的url字段取出来,然后直接生成Request对象。此Request加入到调度队列,等待被调度,执行下载。
file_path(),它的第一个参数request就是当前下载对应的Request对象。这个方法用来返回保存的文件名,直接将图片链接的最后一部分当作文件名即可。它利用split()函数分割链接并提取最后一部分,返回结果。这样此图片下载之后保存的名称就是该函数返回的文件名。
item_complete(),它是当单个Item完成下载时的处理方法。因为并不是每张照片都会下载成功,所以我们需要分析下载结果并剔除下载失败的图片。该方法的第一个参数results就是该Item对应的下载结果,它是一个列表形式,列表每一个元素是一个元组,其中包含了下载成功或失败的信息。这里我们遍历下载结果找出所有下载成功的下载列表。如果列表为空,那么该Item对应的图片下载失败,随机抛出异常DropItem,该Item忽略。否则返回该Item,说明此Item有效。
修改settings.py,设置ITEM_PIPELINES,启用ImagesPipeline

ITEM_PIPELINES = {'BaiduImages.pipelines.BaiduimagesPipeline': 1}

3.4 运行爬虫

scrapy crawl images

3.5 源码记录

3.5.1 Item.py

# -*- coding: utf-8 -*-

# Define here the models for your scraped items
#
# See documentation in:
# https://doc.scrapy.org/en/latest/topics/items.html

from scrapy import Item, Field


class BaiduimagesItem(Item):
    url = Field()
    pass

3.5.2 images.py

# -*- coding: utf-8 -*-
from scrapy import Spider, Request
from urllib.parse import quote
from BaiduImages.items import BaiduimagesItem
import json


class ImagesSpider(Spider):
    name = 'images'
    allowed_domains = ['images.baidu.com']
    start_urls = ['https://images.baidu.com/']

    def parse(self, response):
        images = json.loads(response.body)['data']
        for image in images:
            item = BaiduimagesItem()
            try:
                item['url'] = image.get('thumbURL')
                yield item
            except Exception as e:
                print(e)
        pass

    def start_requests(self):
        data = {'queryWord': '关键词', 'word': '关键词'}
        base_url = 'https://image.baidu.com/search/acjson?tn=resultjson_com&ipn=rj&ct=201326592&is=&fp=result&queryWord='
        for page in range(1, self.settings.get('MAX_PAGE') + 1):
            data['pn'] = page * 30
            url = base_url + quote(data['queryWord']) + '&cl=2&lm=-1&ie=utf-8&oe=utf-8&adpicid=&st=-1&z=&ic=0&word=' + \
                  quote(data['word']) + '&s=&se=&tab=&width=&height=&face=0&istype=2&qc=&nc=1&fr=&expermode=&pn=' + \
                  quote(str(data['pn'])) + '&rn=30&gsm=' + str(hex(data['pn']))
            yield Request(url, self.parse)

3.5.3 pipelines.py

# -*- coding: utf-8 -*-

# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: https://doc.scrapy.org/en/latest/topics/item-pipeline.html
from scrapy import Request
from scrapy.exceptions import DropItem
from scrapy.pipelines.images import ImagesPipeline


class BaiduimagesPipeline(ImagesPipeline):

    def file_path(self, request, response=None, info=None):
        url = request.url
        file_name = url.split('/')[-1]
        return file_name

    def item_completed(self, results, item, info):
        image_paths = [x['path'] for ok, x in results if ok]
        if not image_paths:
            raise DropItem('Image Downloaded Failed')
        return item

    def get_media_requests(self, item, info):
        yield Request(item['url'])

四、 Python踩坑

4.1 URL编码

4.1.1 urlencode

urllib库里面有urlencode函数,可以把key-value这样的键值对转换成我们想要的格式,返回的是a=1&b=2这样的字符串,比如:

如果只想对一个字符串进行urlencode转换,则使用urllib提供的另外一个函数:quote()


3##4.1.2 urldecode
当urlencode之后的字符串传递过来之后,接收完毕就要解码了。urllib提供了unquote()这个函数,没有urldecode()!


参考链接:
https://blog.csdn.net/dodouaj/article/details/54908665
https://blog.csdn.net/qq_25109263/article/details/79445085
http://www.maiziedu.com/wiki/crawler/photograph/
https://blog.csdn.net/qq_32166627/article/details/60882964
https://blog.csdn.net/haoni123321/article/details/15814111

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,761评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,953评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,998评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,248评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,130评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,145评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,550评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,236评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,510评论 1 291
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,601评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,376评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,247评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,613评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,911评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,191评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,532评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,739评论 2 335

推荐阅读更多精彩内容