基于Scrapy的百度图片爬虫

一、准备工作

1.1 激活虚拟环境

activate envname

《基于Scrapy的百度图片爬虫》

1.2 新建Scrapy项目

scrapy startproject projectname

《基于Scrapy的百度图片爬虫》

1.3 新建Spider

scrapy genspider images image.baidu.com

《基于Scrapy的百度图片爬虫》

1.4 PyCharm打开项目

《基于Scrapy的百度图片爬虫》

1.5 选择项目解释器

《基于Scrapy的百度图片爬虫》

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

二、网页分析

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

《基于Scrapy的百度图片爬虫》

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

《基于Scrapy的百度图片爬虫》

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

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

《基于Scrapy的百度图片爬虫》

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

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

《基于Scrapy的百度图片爬虫》

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

《基于Scrapy的百度图片爬虫》

三、 代码编写

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

《基于Scrapy的百度图片爬虫》

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

《基于Scrapy的百度图片爬虫》

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这样的字符串,比如:

《基于Scrapy的百度图片爬虫》

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

《基于Scrapy的百度图片爬虫》

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

《基于Scrapy的百度图片爬虫》

参考链接:
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

    原文作者:鱼鱼鱼筠
    原文地址: https://www.jianshu.com/p/e45d718e2c67
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞