如何写一个简易的爬虫

ReZero lol

如何写一个简易的爬虫

基本概念

有言在先

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

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

啥是爬虫

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

学会爬虫的好处

  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此外,此函数通常功能即为去重后存储

  • Post title:如何写一个简易的爬虫
  • Post author:ReZero
  • Create time:2020-03-08 20:28:00
  • Post link:https://rezeros.github.io/2020/03/08/init-spider/
  • Copyright Notice:All articles in this blog are licensed under BY-NC-SA unless stating additionally.
 Comments