如何写一个简易的爬虫
如何写一个简易的爬虫
基本概念
有言在先
本文只是新手文,望各位指正
本文图片之类链接打不开,可直接科学上网走外链,参考 原博文
啥是爬虫
按一定规则,遵循协议实现的请求模拟接口。
学会爬虫的好处
- 了解一些法律常识
- 吃牢饭
- 学习
web
相关的基础知识
推荐阅读
基础
1 | with open('/usr/bin/ls', 'w') as f: |
上面是一个基础爬虫的所有内容,包括了请求,解析处理以及数据的持久化, 毫无疑问这种爬虫不够快,我们给他改装下,用上多进程和异步协程(python的多线程有全局锁,所以用起来可以说是食之无味),这里推荐阅读 Python 异步协程和多进程使用
多进程协程知识扩展: PENDING
改装过后的爬虫速度上去了,但是不够健壮,爬虫的基本目标数据锁定好后,剩下的工作几乎都是为了让爬虫能够稳定的爬取这些数据,也就是说我们需要应对反爬来制定反反爬策略。
相关技能要求
一般来说目标数据会有两种存在方式,一种是数据直接清晰的描述在
response
中,这种多见于现行的前后端分离模式,该模式下后端往往提供纯数据的接口供前端渲染。 也有另一种方式,就是模板引擎之类直接渲染在页面上的,这类多是采用解析element
对页面进行格式化处理来采摘需要的数据。针对前者没什么好说的,后者就要求至少掌握一种element
的解析方式,这里推荐两种,一种是语义化比较舒服的bs4
, 另一种相对前者略高一点学习成本,有点类似正则自然也具有很高效率的xpath
。至少了解一种持久化的方式,数据爬取后一般都是要存储的,写文件也好,存关系或非关系型数据库也好,总要有种持久化的方式
了解一点你所使用语言的性能相关的知识,爬虫毕竟通常是用来采集大量的数据的,如果没有好的性能,效率堪忧不说,也对反爬方的识别提供了充裕的时间。当然这点并非绝对,纯粹的过快的采集也极易被封杀。
常见反爬和反反爬策略
先说最极端的情况,爬与反爬本来就是个道高魔高的问题,所以一定要把数据持久化,打好标记,做断点续传。
再说一个极端,反爬针对的是爬虫,所以大部分爬虫都是在模仿人的行为来迷惑目标网站。所以这期间就出了一个特类的爬虫,驱动型爬虫,简单说就是控制浏览器行为模仿人的行为来操作,该方法大多数情况下直接绕过了各类网站做的层出不穷的鉴权操作,这方面推荐的了解是
Chrome Headless, Selenium
(很多自动化测试也采用此方法) 此方法过为霸道,霸道总是会有奇效,但此文不做阐述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
,溯源链接,带上就好
CSS
,反反爬的一个难点 。对于解析页面,审查元素是最常用的定位技能,然而用户看到的数据,和定位的元素未必是同一个数据,这点可以利用css
的一些方法来实现 trick(比如元素偏移,伪元素,字符集替换等),这里列举几篇文章:CSS 反爬,CSS 隐藏式,自如反爬 ,SVG 反爬 有兴趣的可以自加搜索。IP 限频。其实封
IP
这种事应该是一种AOE式攻击,可能效果非常显著,但是误伤的几率也应该很大。所以这个没啥好说的,自己从网上找几个免费代理网站,爬下来维护个IP
池(这其实和前面的刷Cookie
,User-Agent
效果是一样的),随机取出来能用的作为活性代理即可。这种方法弊端很明显,免费的代理大家都清楚,不稳定,慢,gg 的快,所以除此之外还有一种骚操作,就是ADSL
拨号,通过拨号上网,既能保障稳定的速度,又能及时更换新的IP
。投毒蜜罐。 这个是我的知识盲区,试想你每次都打印你同学的实验报告,直到有一次被他发现,然后他就偷偷地在里面掺了一段小黄文。这能怎么办?如果商家返回的数据真的是进行了投毒,对数据进行了掺假,这发现了也许还有应对措施,但大多数情况下无法分辨是否有假数据。相对投毒,蜜罐更加直接,发现你是爬虫之后直接为你替换成假数据接口,甚至对你的行为模式进行分析,下次你再用相同的手段去爬取几乎就是难上加难了。
验证码, 这个属于鉴权的一种吧。现在多是采用两种应对模式,一种就是找样例数据,打
tag
,自己去训练模型,对验证码进行识别。另一种就简单暴力,人工打码,上了年纪的人应该都听说过一种兼职叫 刷单,打码平台也是这个道理,他会召集一些可能无聊的人,让他们人工式地去帮忙识别验证码,这种方法的好处是能应对任何人类可以破解的验证码(比如 12306 这种变态的验证),这种方式能想到的坏处就是人力资源可能会越来越贵,所以以后来说可能还是前者可能性更高SQL 注入, 有一说一,爬虫本来就是种注入手段(例如 SqlMap),所以对面说你先动的手你也没啥好委屈的。说到安全,不得不说,很多渗透或者攻击手段,其实都是直接或间接地利用了爬虫。比如常见的工具
setoolkit
中克隆站点的方式,通过爬取模拟目标网站,采用社会工程的方式去钓鱼,诱导用户。再比如批量构造子域名或访问路径,来猜测目标网站的后台地址,又或者拦截请求,直接修改参数进行暴力破解等。JS 加密混淆,反混淆工具推荐: JStillery
图片数据,将数据渲染成图片的形式返回响应。这个没有溯源的办法, 目前可行的都是做
OCR
识别
框架的推荐
如果只是写简单的爬虫,那么推荐
requests
搭配个bs4
就够用了,整挺好如果想用个成熟点的工具,那么就推荐一下
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
scrapy startproject YourProjectName
建立scrapy项目
开始爬虫
scrapy crawl SpiderName
开始执行爬虫 以上仿麻烦
根目录新建执行文件from scrapy.cmdline import execute execute(['scrapy', 'crawl', 'dingdian'])
顶点为spider的name
定义爬取的字段 就比如说爬小说
字段有 小说作者,小说内容等
Item文件下定义class DingdianItem(scrapy.Item): author = scrapy.Field() content = scrapy.Field()
如上,定义一定要继承Item
然后字段皆为Field(), 没有其他类型
比Django要方便的多开始写核心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就可以了
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.