Scrapy爬取动态页面下载图片(以抓取360图片为例)

时间:2021-10-14 08:55:46

当我们想要抓取一个页面的内容时,要做的第一件事不是写代码,而是分析页面,确定这是一个静态页面还是动态页面。抓取静态页面的方法十分简单,直接解析html源码再进行分析解析即可,如果不太明白,可以参考我上篇文章Scrapy抓取豆瓣电影信息,这里我主要讲述一下如何抓取动态页面。

抓取动态页面有两种方法:
第一种方法是采用第三方工具,模拟浏览器的行为,从而加载数据。比如:Selenium、PhantomJs。这种方法的优点在于不必考虑动态页面的各种变化多端,但是性能低。

第二种方法是分析页面,找到对应的请求接口,直接获取数据。这种方法的优点在于性能高,但缺点也显而易见,就是获取API接口比较麻烦。

我这里采用第二种方式抓取动态页面。

1.浏览器打开页面 http://image.so.com/z?ch=beauty,查看网页源代码,发现源码中没有图片信息,这是一个动态加载的页面,而且是ajax异步请求动态页面。

2.分析页面,提取API接口,通过F12,审查元素可以找到。
Scrapy爬取动态页面下载图片(以抓取360图片为例)

3.打开上面的url,发现传入的是json格式的数据,所以之后我们获取到response响应,要先用json.loads()解析数据。
Scrapy爬取动态页面下载图片(以抓取360图片为例)

4.观察API接口,可以发现有几个参数,ch、sn、listtype、temp,通过改变这些参数的值就能获取到不同的内容。

通过分析发现,ch参数代表图片分类,比如beauty就表示美女图片,sn表示图片的编号,比如0就表示1到30之间的图片,30就表示31到60之间的图片。

分析了这些参数,我们就确定了我们需要请求的url地址,此处的url不像静态页面中通过程序自动获取a标签中的href,而是需要我们自动设定,我们可以通过重写start_requests方法指定需要获取的url。

5.编写我们的spider

# -*- coding: utf-8 -*-
from json import loads

import scrapy

from urllib.parse import urlencode

from image360.items import BeautyItem


class ImageSpider(scrapy.Spider):
    name = 'image'
    allowed_domains = ['image.so.com']

    # 重写Spider中的start_requests方法:指定开始url
    def start_requests(self):
        base_url = 'http://image.so.com/zj?'
        param = {'ch': 'beauty', 'listtype': 'new', 'temp': '1'}
        # 可以根据需要爬取不同数量的图片,此处只爬取60张图片
        for page in range(2):
            param['sn'] = page * 30
            full_url = base_url + urlencode(param)
            yield scrapy.Request(url=full_url, callback=self.parse)

    def parse(self, response):
        # 获取到的内容是json数据
        # 用json.loads()解析数据
        # 此处的response没有content
        model_dict = loads(response.text)
        for elem in model_dict['list']:
            item = BeautyItem()
            item['title'] = elem['group_title']
            item['tag'] = elem['tag']
            item['height'] = elem['cover_width']
            item['width'] = elem['cover_height']
            item['url'] = elem['qhimg_url']
            yield item

6.编写item,定义保存的字段


import scrapy


class BeautyItem(scrapy.Item):
    title = scrapy.Field()
    tag = scrapy.Field()
    height = scrapy.Field()
    width = scrapy.Field()
    url = scrapy.Field()

7.编写pipeline,完成数据持久化操作,这里包括下载图片和保存图片信息到mongo中。

Scrapy提供了一个 item pipeline ,来下载属于某个特定项目的图片,比如,当你抓取产品时,也想把它们的图片下载到本地,就可以通过图片管道实现。这在ImagesPipenine类中实现,提供了一个方便并具有额外特性的方法,来下载并本地存储图片。这个类中提供了很多处理图片的方法,想要了解详细内容可以查看官方文档中文版

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

import logging
import pymongo
import scrapy

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

logger = logging.getLogger('SaveImagePipeline')


# 继承ImagesPipenine类,这是图片管道
class SaveImagePipeline(ImagesPipeline):
    """ 下载图片 """
    def get_media_requests(self, item, info):
        # 此方法获取的是requests 所以用yield 不要return
        yield scrapy.Request(url=item['url'])

    def item_completed(self, results, item, info):
        """ 文件下载完成之后,返回一个列表 results 列表中是一个元组,第一个值是布尔值,请求成功会失败,第二个值的下载到的资源 """
        if not results[0][0]:
            # 如果下载失败,就抛出异常,并丢弃这个item
            # 被丢弃的item将不会被之后的pipeline组件所处理
            raise DropItem('下载失败')
        # 打印日志
        logger.debug('下载图片成功')
        return item

    def file_path(self, request, response=None, info=None):
        """ 返回文件名 """
        return request.url.split('/')[-1]


class SaveToMongoPipeline(object):
    """ 保存图片信息到数据库 """
    def __init__(self, mongodb_server, mongodb_port, mongodb_db, mongodb_collection):
        self.mongodb_server = mongodb_server
        self.mongodb_port = mongodb_port
        self.mongodb_db = mongodb_db
        self.mongodb_collection = mongodb_collection

    def open_spider(self, spider):
        # 当spider被开启时,这个方法被调用
        self.connection = pymongo.MongoClient(self.mongodb_server, self.mongodb_port)
        db = self.connection[self.mongodb_db]
        self.collection = db[self.mongodb_collection]

    def close_spider(self, spider):
        # 当spider被关闭时,这个方法被调用。
        self.connection.close()

    # 依赖注入
    @classmethod
    def from_crawler(cls, crawler):
        # cls() 会调用初始化方法
        return cls(crawler.settings.get('MONGODB_SERVER'),
                   crawler.settings.get('MONGODB_PORT'),
                   crawler.settings.get('MONGODB_DB'),
                   crawler.settings.get('MONGODB_COLLECTION'))

    def process_item(self, item, spider):
        post = {'title': item['title'], 'tag': item['tag'],
                'width': item['width'], 'height': item['height'], 'url': item['url']}
        self.collection.insert_one(post)
        return item

8.编写settings,完成配置,此处只把需要配置的内容写了出来。

BOT_NAME = 'image360'

SPIDER_MODULES = ['image360.spiders']
NEWSPIDER_MODULE = 'image360.spiders'

# Crawl responsibly by identifying yourself (and your website) on the user-agent
USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) ' \
             'Chrome/67.0.3396.79 Safari/537.36'

# Obey robots.txt rules
ROBOTSTXT_OBEY = False

# Configure maximum concurrent requests performed by Scrapy (default: 16)
CONCURRENT_REQUESTS = 1

# 随机延迟,设定随机延迟,使爬虫更像浏览器的行为
RANDOMIZE_DOWNLOAD_DELAY = True
# 延迟时间
DOWNLOAD_DELAY = 3

# 数据库信息
MONGODB_SERVER = '服务器地址'
MONGODB_PORT = 27017
MONGODB_DB = 'image360'
MONGODB_COLLECTION = 'image'

# 日志
LOG_LEVEL = 'DEBUG'

# 数字越小越先执行
ITEM_PIPELINES = {
    'image360.pipelines.SaveImagePipeline': 300,
    'image360.pipelines.SaveToMongoPipeline': 330,
}

# 配置图片保存地址,会自动创建文件夹
IMAGES_STORE = './resources'

9.启动spider
在启动spider之前,我们还需要安装几个包,pypiwin32、pilllow、pymongo

pip install pypiwin32
pip install pillow
pip install pymongo

安装完成之后就可以启动spider了

scrapy crawl image

10.查看结果:
获取到的图片返回的内容:
Scrapy爬取动态页面下载图片(以抓取360图片为例)

保存在指定路径的图片:
Scrapy爬取动态页面下载图片(以抓取360图片为例)

一个爬取动态页面并且下载图片的爬虫就完成了,其实写起来很简单,关键是需要分析API接口,重写start_requests方法。