对于 BOSS 直聘这种网站,当程序请求网页后,服务器响应内容包含了整个页面的 HTML 源代码,这样就可以使用爬虫来爬取数据。但有些网站做了一些“反爬虫”处理,其网页内容不是静态的,而是使用 JavaScript 动态加载的,此时的爬虫程序也需要做相应的改进。

使用 shell 调试工具分析目标站点

本项目爬取的目标站点是 https://unsplash.com/,该网站包含了大量高清、优美的图片。本项目的目标是爬虫程序能自动识别并下载该网站上的所有图片。

在开发该项目之前,依然先使用 Firefox 浏览该网站,然后查看该网站的源代码,将会看到页面的 <body.../> 元素几乎是空的,并没有包含任何图片。

现在使用 Scrapy 的 shell 调试工具来看看该页面的内容。在控制台输入如下命令,启动 shell 调试:

scrapy shell https://unsplash.com/

  

执行上面命令,可以看到 Scrapy 成功下载了服务器响应数据。接下来,通过如下命令来尝试获取所有图片的 src 属性(图片都是 img 元素,src 属性指定了图片的 URL):

response.xpath('//img/@src').extract()

执行上面命令,将会看到返回一系列图片的URL,但它们都不是高清图片的 URL。

还是通过"Ctrl+Shift+I"快捷键打开 Firefox 的调试控制台,再次向 https://unsplash.com/ 网站发送请求,接下来可以在 Firefox 的调试控制台中看到如图 1 所示的请求。


图 1 动态获取图片的请求

可见,该网页动态请求图片的 URL 如下:

https://unsplash.com/napi/photos?page=4&per_page=12

上面 URL 中的 page 代表第几页,per_page 代表每页加载的图片数。使用 Scrapy 的 shell 调试工具来调试该网址,输入如下命令:

scrapy shell https://unsplash.com/napi/photos?page=1&per_page=10

  

上面命令代表请求第 1 页,每页显示 10 张图片的响应数据。执行上面命令,服务器响应内容是一段 JSON 数据,接下来在 shell 调试工具中输入如下命令:

>>> import json
>>> len(json.loads(response.text))
10

  

从上面的调试结果可以看到,服务器响应内容是一个 JSON 数组(转换之后对应于 Python 的 list 列表),且该数组中包含 10 个元素。

使用 Firefox 直接请求 https://unsplash.com/napi/photos?page=1&per_page=12 地址(如果希望使用更专业的工具,则可选择 Postman),可以看到服务器响应内容如图 2 所示。


图 2 服务器响应的JSON 数据

在图 2 所示为所有图片的 JSON 数据,每张图片数据都包含 id、created_at(创建时间)、updated_at(更新时间)、width(图片宽度)、height(图片高度)等基本信息和一个 links 属性,该属性值是一个对象(转换之后对应于 Python 的 dict),它包含了 self、html、download、download_location 属性,其中 self 代表浏览网页时的图片的 URL;而 download 才是要下载的高清图片的 URL。

网络爬虫毕竟是针对别人的网站“爬取” 数据的,而目标网站的结构随时可能发生改变,读者应该学习这种分析方法,而不是“生搬硬套”照抄本节的分析结果。

尝试在 shell 调试工具中查看第一张图片的下载 URL,应该在 shell 调试工具中输入如下命令:

>>> json.loads(response.text)[0]['links']['download']
'https://unsplash.com/photos/-RMY4j97SsM/download'

  

与图 2 中 JSON 数据对比不难看出,shell 调试工具输出的第一张图片的下载 URL 与图 2 所显示的第一张图片的下载 URL 完全相同。

由此得到一个结论,该网页加载时会自动向 https://unsplash.com/napi/photos?age=N&per_page=N 发送请求,然后根据服务器响应的 JSON 数据来动态加载图片。

由于该网页是“瀑布流”设计(所谓“瀑布流”设计,就是网页没有传统的分页按钮,而是让用户通过滚动条来实现分页,当用户向下拖动滚动条时,程序会动态载入新的分页),
当我们在 Firefox 中拖动滚动条时,可以在 Firefox 的调试控制台中看到再次向 https://unsplash.com/napi/photos?page=N&per_page=N 发送了请求,
只是 page 参数发生了改变。可见,为了不断地加载新的图片,程序只要不断地向该 URL 发送请求,并改变 page 参数即可。

经过以上分析,下面我们开始正式使用 Scrapy 来实现爬取高清图片。

使用Scrapy 爬取高清图片

按照惯例,使用如下命令来创建一个 Scrapy 项目:

scrapy startproject UnsplashimageSpider

然后在命令行窗口中进入 UnsplashlmageSpider 所在的目录下(不要进入 UnsplashImageSpider\UnsplashImageSpider目录下),执行如下命令来生成 Spider 类:

scrapy genspider unsplash_image 'unsplash.com

  

上面两个命令执行完成之后,一个简单的 Scrapy 项目就创建好了。

接下来需要修改 UnsplashImageSpider\items.py、UnsplashImageSpider\pipelines.py、UnsplashImageSpider\spiders\unsplash_image.py、UnsplashImageSpider\settings.py 文件,将它们全部改为使用 UTF-8 字符集来保存。

现在按照如下步骤来开发该爬虫项目:

  1. 定义 Item 类。由于本项目的目标是爬取高清图片,因此其所使用的 Item 类比较简单,只要保存图片 id 和图片下载地址即可。

    下面是该项目的 Item 类的代码:

    import scrapy
    class ImageItem(scrapy.Item):
    # 保存图片id
    image_id = scrapy.Field()
    # 保存图片下载地址
    download = scrapy.Field()

      

    上面程序为 Item 类定义了两个变量,分别用于保存图片 id 和图片下载地址。

  2. 开发 Spider。开发 Spider 就是指定 Scrapy 发送请求的 URL,并实现 parse(self, response) 方法来解析服务器响应数据。

    下面是该项目的 Spider 程序:

    import scrapy, json
    from UnsplashImageSpider.items import ImageItem
    class UnsplashImageSpider(scrapy.Spider):
    # 定义Spider的名称
    name = 'unsplash_image'
    allowed_domains = ['unsplash.com']
    # 定义起始页面
    start_urls = ['https://unsplash.com/napi/photos?page=1&per_page=12']
    def __init__ (self):
    self.page_index = 1
    def parse(self, response):
    # 解析服务器响应的JSON字符串
    photo_list = json.loads(response.text) # ①
    # 遍历每张图片
    for photo in photo_list:
    item = ImageItem()
    item['image_id'] = photo['id']
    item['download'] = photo['links']['download']
    yield item
    self.page_index += 1
    # 获取下一页的链接
    next_link = 'https://unsplash.com/napi/photos?page='\
    + str(self.page_index) + '&per_page=12'
    # 继续获取下一页的图片
    yield scrapy.Request(next_link, callback=self.parse)

      

    上面程序中第 9 行代码指定的 URL 是本项目爬取的第一个页面,由于该页面的响应是一个 JSON 数据,因此程序无须使用 XPath 或 CSS 选择器来“提取”数据,而是直接使用 json 模块的 loads() 函数来加载该响应数据即可。

    在获取 JSON 响应数据之后,程序同样将 JSON 数据封装成 Item 对象后返回给 Scrapy 引擎。

    Spider 到底应该使用 XPath 或 CSS 选择器来提取响应数据,还是使用 JSON,完全取决于目标网站的响应内容,怎么方便怎么来!总之,提取到数据之后,将数据封装成 Item 对象后返回给 Scrapy 引擎就对了。

    上面程序中倒数第 2 行代码定义了加载下一页数据的 URL,接下来使用 scrapy.Request 向该 URL 发送请求,并指定使用 self.parse 方法来处理服务器响应内容,这样程序就可以不断地请求下一页的图片数据。

  3. 开发 Pipeline。Pipeline 负责保存 Spider 返回的 Item 对象(封装了爬取到的数据)。本项目爬取的目标是图片,因此程序得到图片的 URL 之后,既可将这些 URL 地址导入专门的下载工具中批量下载,也可在 Python 程序中直接下载。

    本项目的 Pipeline 将使用 urllib.request 包直接下载。下面是该项目的 Pipeline 程序:

    from urllib.request import *
    class UnsplashimagespiderPipeline(object):
    def process_item(self, item, spider):
    # 每个item代表一个要下载的图片
    print('----------' + item['image_id'])
    real_url = item['download'] + "?force=true"
    try:
    pass
    # 打开URL对应的资源
    with urlopen(real_url) as result:
    # 读取图片数据
    data = result.read()
    # 打开图片文件
    with open("images/" + item['image_id'] + '.jpg', 'wb+') as f:
    # 写入读取的数据
    f.write(data)
    except:
    print('下载图片出现错误' % item['image_id'])

      

    上面程序中第 7 行代码用于拼接下载图片的完整地址。可能有读者会问,为何要在图片下载地址的后面追加“?force=true”?这并不是本项目所能决定的,读者可以把鼠标指针移动到 https://unsplash.com 网站中各图片右下角的下载按钮上,即可看到各图片的下载地址都会在 download 后追加“?force=true”,此处只是模拟这种行为而已。

    程序中第 11 行代码使用 urlopen() 函数获取目标 URL 的数据,接下来即可读取图片数据,并将图片数据写入下载的目标文件中。

经过上面 3 步,基于 Scrapy 开发的高清图片爬取程序基本完成。接下来依然需要对 settings.py 文件进行修改,即增加一些自定义请求头(用于模拟浏览器),设置启用指定的 Pipeline。下面是本项目修改后的 settings.py 文件:

BOT_NAME = 'UnsplashImageSpider'
SPIDER_MODULES = ['UnsplashImageSpider.spiders']
NEWSPIDER_MODULE = 'UnsplashImageSpider.spiders'
ROBOTSTXT_OBEY = True
# 配置默认的请求头
DEFAULT_REQUEST_HEADERS = {
"User-Agent" : "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:61.0) Gecko/20100101 Firefox/61.0",
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'
}
# 配置使用Pipeline
ITEM_PIPELINES = {
'UnsplashImageSpider.pipelines.UnsplashimagespiderPipeline': 300,
}

  

至此,这个可以爬取高清图片的爬虫项目开发完成,读者可以在 UnsplashlmageSpider 目录下执行如下命令来启动爬虫。

scrapy crawl unsplash_image
#或者
scrapy runspider unsplash_image

 区别:

命令

说明 是否需要项目 示例
runspider  未创建项目的情况下,运行一个编写在Python文件中的spider  no  $ scrapy runspider myspider.py
crawl  使用spider进行爬取  yes $ scrapy crawl myspider

 

运行该爬虫程序之后,可以看到在项目的 images 目录下不断地增加新的高清图片(对图片的爬取速度在很大程度上取决于网络下载速度),这些高清图片正是 https://unsplash.com 网站中所展示的图片。

Python Scrapy突破反爬虫机制(项目实践)的更多相关文章

  1. 第7章 Scrapy突破反爬虫的限制

    7-1 爬虫和反爬的对抗过程以及策略 Ⅰ.爬虫和反爬虫基本概念 爬虫:自动获取网站数据的程序,关键是批量的获取. 反爬虫:使用技术手段防止爬虫程序的方法. 误伤:反爬虫技术将普通用户识别为爬虫,如果误 ...

  2. Scrapy突破反爬虫的限制

    随机切换UserAgent https://github.com/hellysmile/fake-useragent scrapy使用fake-useragent 在全局配置文件中禁用掉默认的UA,将 ...

  3. Scrapy爬取美女图片第四集 突破反爬虫(上)

     本周又和大家见面了,首先说一下我最近正在做和将要做的一些事情.(我的新书<Python爬虫开发与项目实战>出版了,大家可以看一下样章) 技术方面的事情:本次端午假期没有休息,正在使用fl ...

  4. python 爬虫 urllib模块 反爬虫机制UA

    方法: 使用urlencode函数 urllib.request.urlopen() import urllib.request import urllib.parse url = 'https:// ...

  5. Python编程:从入门到项目实践高清版附PDF百度网盘免费下载|Python入门编程免费领取

    百度网盘:Python编程:从入门到项目实践高清版附PDF免费下载 提取码:oh2g   第一部分 基础知识第1章 起步 21.1 搭建编程环境 21.1.1 Python 2和Python 3 21 ...

  6. 使用Python 爬取 京东 ,淘宝。 商品详情页的数据。(避开了反爬虫机制)

    以下是爬取京东商品详情的Python3代码,以excel存放链接的方式批量爬取.excel如下 代码如下 from selenium import webdriver from lxml import ...

  7. 深入细枝末节,Python的字体反爬虫到底怎么一回事

    内容选自 即将出版 的<Python3 反爬虫原理与绕过实战>,本次公开书稿范围为第 6 章——文本混淆反爬虫.本篇为第 6 章中的第 4 小节,其余小节将 逐步放送 . 字体反爬虫开篇概 ...

  8. python基于scrapy框架的反爬虫机制破解之User-Agent伪装

    user agent是指用户代理,简称 UA. 作用:使服务器能够识别客户使用的操作系统及版本.CPU 类型.浏览器及版本.浏览器渲染引擎.浏览器语言.浏览器插件等. 网站常常通过判断 UA 来给不同 ...

  9. Python 有道翻译 爬虫 有道翻译API 突破有道翻译反爬虫机制

    py2.7 #coding: utf-8 import time import random import hashlib import requests while(1): url = 'http: ...

随机推荐

  1. 网络协议 12 - HTTP 协议:常用而不简单

    系列文章传送门: 网络协议 1 - 概述 网络协议 2 - IP 是怎么来,又是怎么没的? 网络协议 3 - 从物理层到 MAC 层 网络协议 4 - 交换机与 VLAN:办公室太复杂,我要回学校 网 ...

  2. springboot~内嵌redis的使用

    对于单元测试来说,我们应该让它尽量保持单一环境,不要与网络资源相通讯,这样可以保证测试的稳定性与客观性,对于springboot这个框架来说,它集成了单元测试JUNIT,同时在设计项目时,你可以使用多 ...

  3. 实例分析C程序运行时的内存结构

      先验知识 静态变量存储在静态存储区,局部变量存储在动态存储区(栈),代码存放在代码区 寄存器,EBP指向栈底,ESP指向栈顶,EIP指向正在执行指令的下一条指令,三个寄存器中保存的都是地址,32位 ...

  4. javascript小记四则:用JS写一个滚动横条文字,可以根据需要进行修改;

    网页上的一些广告文字,一直会滚动是怎么做到的,今天给大家演示下,非常简单,源码如下(本案例是在.net平台上,但HTML是通用的): <!DOCTYPE html> <html> ...

  5. IntelliJ IDEA如何激活?

    本文使用的IDEA的版本是:14.0.3 下载IDEA授权服务器(下载地址见最后),并解压,打开解压后的IntelliJIDEALicenseServer目录,可以看到如下的两个文件: Intelli ...

  6. Python二级-----------程序冲刺1

    1. 仅使用 Python 基本语法,即不使用任何模块,编写 Python 程序计算下列数学表达式的结果并输出,小数点后保留3位.‪‬‪‬‪‬‪‬‪‬‮‬‪‬‫‬‪‬‪‬‪‬‪‬‪‬‮‬‪‬‪‬‪‬‪ ...

  7. nginx系列14:对HTTP协议的反向代理proxy模块

    proxy_pass指令 URL参数规则 需要注意的是,url中携带和不携带URI时发往上游请求的行为不同!

  8. git操作常用命令

    一.使用git 1.git是什么? Git是目前世界上最先进的分布式版本控制系统. SVN与Git的最主要的区别? SVN是集中式版本控制系统,版本库是集中放在中央服务器的,而干活的时候,用的都是自己 ...

  9. C# 中一些类关系的判定方法

    1.  IsAssignableFrom实例方法 判断一个类或者接口是否继承自另一个指定的类或者接口. public interface IAnimal { } public interface ID ...

  10. Python 基于python操纵zookeeper介绍

    基于python操纵zookeeper介绍 by:授客  QQ:1033553122 测试环境 Win7 64位 Python 3.3.4 kazoo-2.6.1-py2.py3-none-any.w ...