ReZero's Utopia.

如何写一个简易的爬虫

Word count: 3.1kReading time: 11 min
2020/03/08 Share

如何写一个简易的爬虫

基本概念

有言在先

本文只是新手文,望各位指正

本文图片之类链接打不开,可直接科学上网走外链,参考 原博文

啥是爬虫

按一定规则,遵循协议实现的请求模拟接口。

学会爬虫的好处

  1. 了解一些法律常识
  2. 吃牢饭
  3. 学习 web 相关的基础知识

推荐阅读

robots 协议

Requests 官网文档

大才哥的 Blog

Scrapy 官网文档

基础

1
2
with open('/usr/bin/ls', 'w') as f:
f.write(parse(request.get("http://api.com?id=1' or 1='1)))

上面是一个基础爬虫的所有内容,包括了请求,解析处理以及数据的持久化, 毫无疑问这种爬虫不够快,我们给他改装下,用上多进程和异步协程(python的多线程有全局锁,所以用起来可以说是食之无味),这里推荐阅读 Python 异步协程和多进程使用


多进程协程知识扩展: PENDING


改装过后的爬虫速度上去了,但是不够健壮,爬虫的基本目标数据锁定好后,剩下的工作几乎都是为了让爬虫能够稳定的爬取这些数据,也就是说我们需要应对反爬来制定反反爬策略。

相关技能要求

  1. 一般来说目标数据会有两种存在方式,一种是数据直接清晰的描述在 response 中,这种多见于现行的前后端分离模式,该模式下后端往往提供纯数据的接口供前端渲染。 也有另一种方式,就是模板引擎之类直接渲染在页面上的,这类多是采用解析 element 对页面进行格式化处理来采摘需要的数据。针对前者没什么好说的,后者就要求至少掌握一种 element 的解析方式,这里推荐两种,一种是语义化比较舒服的 bs4, 另一种相对前者略高一点学习成本,有点类似正则自然也具有很高效率的 xpath

  2. 至少了解一种持久化的方式,数据爬取后一般都是要存储的,写文件也好,存关系或非关系型数据库也好,总要有种持久化的方式

  3. 了解一点你所使用语言的性能相关的知识,爬虫毕竟通常是用来采集大量的数据的,如果没有好的性能,效率堪忧不说,也对反爬方的识别提供了充裕的时间。当然这点并非绝对,纯粹的过快的采集也极易被封杀。

常见反爬和反反爬策略

  1. 先说最极端的情况,爬与反爬本来就是个道高魔高的问题,所以一定要把数据持久化,打好标记,做断点续传。

  2. 再说一个极端,反爬针对的是爬虫,所以大部分爬虫都是在模仿人的行为来迷惑目标网站。所以这期间就出了一个特类的爬虫,驱动型爬虫,简单说就是控制浏览器行为模仿人的行为来操作,该方法大多数情况下直接绕过了各类网站做的层出不穷的鉴权操作,这方面推荐的了解是 Chrome Headless, Selenium(很多自动化测试也采用此方法) 此方法过为霸道,霸道总是会有奇效,但此文不做阐述

  3. header 的重要性。http 协议的 request 基本分为三部分, start line, header, body 三部分中body往往封装业务数据作为请求体,start line 几乎是必要的基本信息,那么剩下的 header 部分,就是最适合做验证的地方,对爬虫来说,很重要的一点就是模拟请求,而模拟请求的难点就是 header 的构造。如果全量模拟请求的 header, 那基本得到的就是相同的响应

    • 最基本的反爬操作就是使用随机的 User-Agent 来进行访问,基本两种最为适用,一种是通用的浏览器 User-Agent,另一种是常见的搜索引擎 User-Agent。 看起来后者更有效,其实不然,只需要反爬方简单的nslookup 下几乎一个正则就能判定真伪。而前者才是更通用的选择,毕竟用户就是用的这些通用浏览器。此外有些爬虫可能在 PC 端不好爬,可以通过更换该属性切换另个平台比如手机端进行尝试,有可能有奇效。

    • Cookie, 这个就是过鉴权的方式之一,传统的 web 应用往往基于 Cookie 来存放验证信息,所以爬虫方只要获取一个活性 Cookie 就可以,而为了维护 Cookie 的有效性, 可以考虑去维护一个 Cookie 池,定期取用活性的 Cookie, 清理或刷新失效的 Cookie

    • Referer,溯源链接,带上就好

  4. CSS,反反爬的一个难点 。对于解析页面,审查元素是最常用的定位技能,然而用户看到的数据,和定位的元素未必是同一个数据,这点可以利用 css 的一些方法来实现 trick(比如元素偏移,伪元素,字符集替换等),这里列举几篇文章:CSS 反爬CSS 隐藏式自如反爬SVG 反爬 有兴趣的可以自加搜索。

  5. IP 限频。其实封 IP 这种事应该是一种AOE式攻击,可能效果非常显著,但是误伤的几率也应该很大。所以这个没啥好说的,自己从网上找几个免费代理网站,爬下来维护个 IP 池(这其实和前面的刷 Cookie, User-Agent 效果是一样的),随机取出来能用的作为活性代理即可。这种方法弊端很明显,免费的代理大家都清楚,不稳定,慢,gg 的快,所以除此之外还有一种骚操作,就是 ADSL 拨号,通过拨号上网,既能保障稳定的速度,又能及时更换新的 IP

  6. 投毒蜜罐。 这个是我的知识盲区,试想你每次都打印你同学的实验报告,直到有一次被他发现,然后他就偷偷地在里面掺了一段小黄文。这能怎么办?如果商家返回的数据真的是进行了投毒,对数据进行了掺假,这发现了也许还有应对措施,但大多数情况下无法分辨是否有假数据。相对投毒,蜜罐更加直接,发现你是爬虫之后直接为你替换成假数据接口,甚至对你的行为模式进行分析,下次你再用相同的手段去爬取几乎就是难上加难了。

  7. 验证码, 这个属于鉴权的一种吧。现在多是采用两种应对模式,一种就是找样例数据,打tag,自己去训练模型,对验证码进行识别。另一种就简单暴力,人工打码,上了年纪的人应该都听说过一种兼职叫 刷单,打码平台也是这个道理,他会召集一些可能无聊的人,让他们人工式地去帮忙识别验证码,这种方法的好处是能应对任何人类可以破解的验证码(比如 12306 这种变态的验证),这种方式能想到的坏处就是人力资源可能会越来越贵,所以以后来说可能还是前者可能性更高

  8. SQL 注入, 有一说一,爬虫本来就是种注入手段(例如 SqlMap),所以对面说你先动的手你也没啥好委屈的。说到安全,不得不说,很多渗透或者攻击手段,其实都是直接或间接地利用了爬虫。比如常见的工具 setoolkit 中克隆站点的方式,通过爬取模拟目标网站,采用社会工程的方式去钓鱼,诱导用户。再比如批量构造子域名或访问路径,来猜测目标网站的后台地址,又或者拦截请求,直接修改参数进行暴力破解等。

  9. JS 加密混淆,反混淆工具推荐: JStillery

  10. 图片数据,将数据渲染成图片的形式返回响应。这个没有溯源的办法, 目前可行的都是做 OCR 识别

框架的推荐

  1. 如果只是写简单的爬虫,那么推荐 requests 搭配个 bs4 就够用了,整挺好

  2. 如果想用个成熟点的工具,那么就推荐一下 scrapy ,当然仍强烈推荐阅读官网文档

Scrapy

  • 初始化时核心部分 Engine 会从 Spider 处获取初始请求以开始作为整个流程的起点。

  • 上述的请求会入队 Scheduler

  • Engine 真正取请求进行工作是从 Scheduler 获取的。

  • Engine 将请求发送到下载器。

  • 请求完成后,Downloader 会生成一个响应结果并将其回复给 Engine。

  • Engine 接收响应,并将其发送到 Spider 进行处理

  • Spider 将响应处理成两部分,一部分是解析后的需要数据作为 Item 另一部分是新的请求。

  • Engine 将处理后的 Item 发送到 Item Pipelines,然后将处理后的请求发送到 Scheduler。

  • 重复上述整个流程,直到 Scheduler 无新的请求为止。

其实图片已经描述的很清楚了,这里只是复述了一遍。总体来说框架的结构还是很通俗易懂的,这里有两个特殊的中间件需要关注下:一个是 Spider 和 Engine 的中间件,这个中间件主要可以用来做发起请求的 post_process 或者处理一些异常之类的;另一个中间件是下载器附近那个,这个可以用来过滤请求,或者改变回复响应之类的。

闲话部分

前面介绍的大多是基于 http 协议的爬虫,但像是直播弹幕,聊天记录等等这些一般是通过 websocket 协议去实现的,这里其实道理都是一样的,也推荐篇文章 弹幕爬取

另外爬虫的实践也推荐一下,很有意思的分析 新浪微博模拟登录

Scrapy notes

  1. scrapy startproject YourProjectName

    建立scrapy项目
  2. 开始爬虫 scrapy crawl SpiderName 开始执行爬虫 以上仿麻烦
    根目录新建执行文件

    from scrapy.cmdline import execute
    execute(['scrapy', 'crawl', 'dingdian'])

    顶点为spider的name

  3. 定义爬取的字段 就比如说爬小说
    字段有 小说作者,小说内容等
    Item文件下定义

    class DingdianItem(scrapy.Item):
        author = scrapy.Field()
        content = scrapy.Field()

    如上,定义一定要继承Item
    然后字段皆为Field(), 没有其他类型
    比Django要方便的多

  4. 开始写核心spider部分 spider以start_requests为初始函数,该函数必须yield一个可迭代对象
    比如Request(url, call_back, meta)
    参数说明url,即需要Request.get 的内容,call_back是一个parse函数,该函数可接受一个由刚才的get获取的response, meta是由上一个Request往这传的时候可以带上的参数 parse 最终要yield 或 return 一个或多个 Item,来进行后续处理

    这里有个巨大的

    response尽量一次处理,不要在多个parse之间执行,因为所有的url,但凡被Request过,不会被二次请求,简单来说,url通常不能为response.url,如果你跟我一样踩到这个坑,注意这个解决方法

Scrapy的官方文档:
http://doc.scrapy.org/en/latest/topics/request-response.html#scrapy.http.Request
Request函数在文档中的定义:
class scrapy.http.Request(url[, callback, method=’GET’, headers, body, cookies, meta, encoding=’utf-8’, priority=0, dont_filter=False, errback])
在这儿, request的 dont_filter 设置为True就可以了

  1. Item被收集完成后,进入pipeline,
    这里就进行最后的处理了,可以将Item的内容提取出来,进行数据库等的存储
    模板:

    class DingdianPipeline(object):

    def process_item(self, item, spider):
        # deferToThread(self._process_item, item, spider
        if isinstance(item, DcontentItem):
            url = item['chapterurl']
            name_id = item['id_name']
            num_id = item['num']
            xs_chaptername = item['chaptername']
            xs_content = item['chaptercontent']
            Sql.insert_novel(name, content, name_id, num_id, url)
            print('小说存储完毕')
            return item

def process_item(self, item, spider):该方法必须重写,且return item此外,此函数通常功能即为去重后存储

CATALOG
  1. 1. 如何写一个简易的爬虫
    1. 1.1. 基本概念
      1. 1.1.1. 有言在先
      2. 1.1.2. 啥是爬虫
      3. 1.1.3. 学会爬虫的好处
      4. 1.1.4. 推荐阅读
      5. 1.1.5. 基础
      6. 1.1.6. 相关技能要求
      7. 1.1.7. 常见反爬和反反爬策略
      8. 1.1.8. 框架的推荐
      9. 1.1.9. 闲话部分
    2. 1.2. Scrapy notes
      1. 1.2.0.1. 这里有个巨大的
  2. 1.3. 坑: