社区所有版块导航
Python
python开源   Django   Python   DjangoApp   pycharm  
DATA
docker   Elasticsearch  
aigc
aigc   chatgpt  
WEB开发
linux   MongoDB   Redis   DATABASE   NGINX   其他Web框架   web工具   zookeeper   tornado   NoSql   Bootstrap   js   peewee   Git   bottle   IE   MQ   Jquery  
机器学习
机器学习算法  
Python88.com
反馈   公告   社区推广  
产品
短视频  
印度
印度  
Py学习  »  Python

《Python爬虫从入门到入狱》学习札记 | Python 主题月

coder-pig • 4 年前 • 946 次点击  
阅读 370

《Python爬虫从入门到入狱》学习札记 | Python 主题月

本文正在参加「Python主题月」,详情查看 活动链接

千呼万唤始出来,掘金Python主题月,喜大普奔👏👏👏,恰逢整理技能树,Python爬虫也在其中,只是优先级较靠后。

时不待我,只争朝夕,趁着活动先把它肝了,整篇Python爬虫入门的学习总结,希望对有意学习Python爬虫却不知道从何入手的朋友有所裨益。内容较多,建议点赞收藏,闲暇时再细细品味。


0x1、侃侃而谈

1. 与Python的"一帘幽梦"

接触Python几年有矣,不过一直都处于初窥门径的阶段,主业Android开发,Python只是兴趣使然,偶尔写写自动化脚本,简化工作,偶尔帮人写下爬虫,捞点小钱,仅此而已。

除了够用外,笔者没往下深刨Python的原因:

  • 要恰饭 → 没有环境支持,导致没大的Python项目经验,跳槽得从0开始,尽管Android现在好像不怎么景气,还很卷,但是那个年限在那里,混口饭吃还是可以的;
  • 时间精力有限 → 一旦做出学习某块知识的选择,就付出了暂时无法学习其他知识的机会成本。既然下一份工作还是Android,肯定是优先巩固Android相关的,会Python是加分,但加不了多少分。

前年机缘巧合出了本Python爬虫入门的实体书,有幸被清华大学大数据研究院选做教材「嘿嘿」, 听说还往他们合作的高校推荐,不知道挂了我学位证的母校有没有位列其中呢?哈哈!

(恰逢Ptython主题月 + 我党100周年,送两本 庆祝下,评论区抽一波~)

尔后对Python的学习毫无建树,书出版后,陆续收到读者和老师的反馈,让我意识到实体书存在的两个问题:

  • 时效性:IT更新日新月异,内容和例子都是速朽的,更新只能重新出版,走流程时间太长;
  • 趣味性:书面化限制较多,不能白话文 + 表情包,没我博客的文章有趣;

脑子一热,开始在公号上更Python教程,对书本内容的更新、补充及扩展,坚持了一段时间。后面没啥空闲时间,认真写的文章没得到较好的反馈,对自己技术的提升也没帮助,就太监了。

(怀念当初实习在南方软件园肝Android教程肝到晚上十一二点,无忧无虑的日子真好啊~)

很庆幸在老东家摸鱼的时候遇见Python,学习Python,爱上Python,她 语法简单容易上手代码优雅功能强大类库丰富社区活跃,任谁看都不禁心猿意马啊!人生苦短,我用Python,这就是我和Python的一帘幽梦。


2、"如火如荼" 的Python课

Python本是一门开发语言,近几年却突然火了起来,学Python广告如雨后春笋般,席卷朋友圈、恰饭公号、抖音。铺天盖地的广告,让 Python 这个词走进了普罗大众的视野,看似掀起了一股 全民学Python的浪潮,实则是 一场割韭菜的狂欢

怎么割?且听我娓娓道来:

吸引用户注意

标题和开头制造焦虑,如职场人最关心的成长、薪资、跳槽等问题,蹭热点,反问句式开头,傻雕剧情引入等,感受下:

  • 工作 3 年连一个实习生都不如……
  • 疫情期间,看到了同事的工资后,我退出了群聊
  • 说了 1000 次年后要辞职的那批人,现在动都不敢动
  • 足不出户的日子里,我发现自己被同龄人狠狠抛弃了
  • 爆红的李子柒,让我越想越后怕……
  • 来公司几个月连升两级,还不是因为长得漂亮……
  • 在职场,不会 python 有多吃亏?

花式戳痛点引需求

指出目标用户面临的现实痛点问题,感受下:

  • 老板太严苟,专门找员工茬
  • 钱少事多,拿着卖白菜的前,操着卖白X的心
  • 同事间勾心斗角,努力大半年还不如刚来的信任
  • 发展空间小,每天都做重复的事情,无效加班...
  • 职场上没竞争力,体力拼不过大学生,资源拼不过老同事
  • 你要悄悄学Python,然后惊艳所有人!
  • 求职工作有帮助,学完Python薪资加不停...

扩大目标人群

受众越多,就可能带来更多的转化,感受下:

  • 你会想:学编程不就成了程序员吗?其实在这个时代真不是这样。
  • 做为职场必备技能,大部分行业都已在招聘 ID 中,纷纷给出了 XXX 这样的招聘条件
  • 下到13岁,上到68岁,大家都在学Python
  • 学Python可以帮助做金融的人XXX,帮助做电商的人XXX,帮助做社群的人XXX

呈现产品差异化

突出产品不同于其他课程的点,让用户选择它的课程,感受下:

  • 零基础、情景教学、在线实操;
  • 编程小白从"不知道"到"知道"
  • 刷个短视频的时间就能学完一节课;
  • 三天入门Python,一行代码打开上帝模式!
  • 助教老师全程指导、像玩游戏一样

强化产品功效

给用户造梦,强调收益,学完之后会怎样,感受下:

简直成为用户口中的大神,用几行代码就能解决他们处理半天的工作……

价格拆解+福利,促成转化

临门一脚,给用户立即购买的理由,利用紧迫感来促成转化,如强调价格是专项价格,仅限前100,只要8.9元(现在有些课程直接0元学,可见割韭菜的竞争有多激烈)。

前六步其实只是套路的开始,更深的套路还在后面~

强化用户学习动机

入群后给你科(xi)普(nao)学Python有多厉害,不断激发你的需求和兴趣,增强你的学习动机,感受下:

  • 晒学员案例,强调菜鸟学完有多牛逼,工作效率有多高;
  • 甩Python岗位薪资,告诉你学Python就业有多容易,薪资有多高;
  • 扩大目标受众,强调谁都能学,谁学都能赚,再不学就要被时代、被同龄人抛弃...

让用户感觉能轻松做到

简单易行给用户参与感,将学习门槛和难度设置得很低:

  • 连软件都不需要下载,打开网页就可以参加学习,课程是文字交互式(聊天)的学习,对0基础同学非常友好;
  • 帮你把代码也写好了,你只需要点个 运行 就能出运行结果;
  • 课后练习题也只是复制粘贴,改动改动,就行了。

鼓励分享给用户成就感

  • 班主任会让学员在学习完一关后将代码的运行结果发到群里,发了的学员能获得课程资料包奖励;
  • 分享链接时还会显示每个人一共运行了几行代码,让人忍不住产生攀比心理;
  • 对于没有完成当天任务的人来说更是一种激励;

送优惠券触发用户转化

体验课临近结束,班主任趁热打铁,开始给大家介绍"正价课程"

  • 正价一门999,团购两门课程,总共有500块的优惠,除此之外还能叠加200的优惠券,最后只要1298就能喜提两门课程,不过优惠券数量有限,只有50张,先到先得;
  • 考虑到不少学员是略微拮据的学生党,还提出支持花呗和信用卡分期;
  • 学员报名后可以领取雨伞、帆布袋等小礼物,以统计礼物的名义,让学员在群里接龙晒单,营造出课程热卖的氛围;
  • 私聊没在群里说话的学员,说优惠券可能不够用,将优先给到已经完课的同学,敦促赶紧上完课的同时,安利你买课

利用 福格模型 (充分的动机 + 完成这一行为的能力 + 触发用户付诸行动的因素),两套组合拳下来,韭菜割得盘满钵满。

(上述内容摘自:攻陷朋友圈的8.9元Python小课,有哪些利用“人性”的新套路?,原文图文解读,阅读体验更佳~)

这让我想起了一位朋友(非IT从业者),上了几节课后觉得编程就那样,简单得不得了,花大价钱买了正课,扬言学几个月后要吊打我,不知道现在水平如何,咋也不敢问,万一等下真被吊打了,就不好了是吧?

想被割韭菜的朋友,听叔一句劝,Python课这水太深了,你把握不住!

  • 如果真是对Python感兴趣,想学来玩玩可以,建议你 直接白嫖 大佬们分享的视频教程(B站上一堆);
  • 如果想着学完这些良莠不齐的速成班后,能赚w,我劝你早点打消这个念头;

外行看热闹,内行看门道,Python课类型看似琳琅满目高大上:爬虫、数据分析、OA自动化办公、人工智能等,实际上就是教你 某几个Python库 的使用,如pandas,numpy,matplotlib等。而这些,网上都有,搜索引擎搜下关键词,一堆。

编程语言,只是一个工具,库更是如此,要学的东西多着呢,编程能力的养成是由时间、经历和智慧堆积而成的,上几个月速成班,等于人家工作几年,有点想得太美好了吧!


3、明哲保身避免 "牢狱之灾"

偶尔有大数据公司被端,无良流量自媒体为博眼球,夸大事实,一句 爬虫玩得好,牢饭吃得早,把想学爬虫萌新吓得瑟瑟发抖,生怕因为自己写的爬虫被拷进去,嘤嘤嘤,害怕,我:

说实话,以大部分萌新的技术能力着有些实想太多,这种妄下定论的方式跟 吃完虾再吃维生素C会砒霜中毒 理论一样,脱离剂量谈毒性——都是耍流氓

从技术中立角度而言,爬虫技术本身并无违法违规之处,爬什么,怎么爬才是导致锒铛入狱的罪魁祸首。Github上有个库记录了国内爬虫开发者涉诉与违规相关的新闻、资料与法律法规: github.com/HiddenStraw…

节省读者时间,直接归纳下:

① 无视robots协议,爬不给爬的数据

robots.txt,纯文本文件,网站管理者可在此文件中声明不想被搜索引擎访问的部分,或指定搜索引擎只收录的指定内容,语法简单:

  • 通配符(*) → 匹配0个或多个任意字符;
  • 匹配符($) → 匹配URL结尾的字符;
  • User-agent → 搜索引擎爬虫的名字,各大搜索引擎都有固定的名字,如百度Baisuspider,如果该项为*(通配符) 表示协议对任何搜索引擎爬虫均有效;
  • Disallow → 禁止访问的路径;
  • Allow → 允许访问的路径;

以掘金的robots.txt为例:

不过这个协议可以说是 君子协议,防君子不防小人,无视Robots协议随意抓取网站内容,将 涉嫌 构成对《反不正当竞争法》的第二条的违反,即违反诚实信用原则和商业道德的不正当竞争行为。

② 强行突破网站设置的技术措施

网站一般都会做下反爬,以减少爬虫批量访问对网站带来的巨大压力和负担。爬虫开发者通过技术手段绕过反爬,客观影响了网站的正常运行(甚至搞挂了),适用《反不正当竞争法》第十二条(四) 其他妨碍、破坏其他经营者合法提供的网络产品或者服务正常运行的行为。

而强行突破某些特定被爬放的技术措施,还可能构成刑事犯罪行为:

  • 《刑法》第二百八十五条:违反规定侵入国家事务、国防建设、尖端科学技术领域的计算机信息系统的,不论情节严重与否,构成非法侵入计算机信息系统罪。
  • 《刑法》第二百八十六条还规定,违反国家规定,对计算机信息系统功能进行删除、修改、增加、干扰,造成计算机信息系统不能正常运行,后果严重的,构成犯罪,处五年以下有期徒刑或者拘役;后果特别严重的,处五年以上有期徒刑。
  • 违反国家规定,对计算机信息系统中存储、处理或者传输的数据和应用程序进行删除、修改、增加的操作,后果严重的,也构成犯罪,依照前款的规定处罚。

这里还要警惕一点:为违法违规组织提供爬虫相关服务,间接的也可能要负刑事责任,案例里的极验破解者被抓就是模板,尽管技术本身无罪,但是你开发出来了,被犯罪分子利用了,一样有责任。( 这也是我之前写的一篇《如何用Python投机倒把几天“暴富”》 不提供投注脚本的原因~)

③ 爬取特定类型的信息

一、用户的个人隐私

抓取用户隐私信息,或在抓取后公开传播,对用户造成损坏后果的,可能侵犯了用户的隐私权。

二、用户的个人信息

  • 《民法总则》第111条:任何组织和个人需要获取他人个人信息的,应当依法取得并确保信息安全。不得非法收集、使用、加工、传输他人个人信息。
  • 《网络安全法》第四十四条:任何个人和组织不得窃取或者以其他非法方式获取个人信息。
  • 《刑法》修正案(九)第二百五十三条:违反国家有关规定,向他人出售或者提供公民个人信息,情节严重的,构成犯罪;在未经用户许可的情况下,非法获取用户的个人信息,情节严重的也将构成“侵犯公民个人信息罪”。

三、著作权法保护的作品

  • 就网页 访问行为 而言,爬虫本身进是对人类访问行为的模拟,爬虫访问人工能访问的信息,不构成侵权。绕过限制访问特定用户才能解除的信息,爬虫访问行为就有可能涉嫌破坏技术措施的违法或者侵权行为。
  • 就数据 保存行为 而言,抓取行为的本质上是对信息的复制,此行为有可能侵犯著作权人的复制权,我国对临时复制的行为持宽容态度。
  • 就数据 提取和使用行为 而言,如果爬虫控制者在自己的网站上公开传播抓取的信息,则可能进一步侵犯信息网络传播权。

四、商业秘密

《反不正当竞争法》第九条:以不正当手段获取他人商业秘密的行为即已经构成侵犯商业秘密。而后续如果进一步利用,或者公开该等信息,则构成对他人商业秘密的披露和使用,同样构成对权利人的商业秘密的侵犯。

五、反不正当竞争保护的数据

未经许可抓取、使用网站中数据的行为,损害了网站的竞争优势,从而构成不正当竞争。这里的数据可能是由用户生成的,无版权,但是作为网站的主要竞争力来源。如抓取大众点评、知乎等UGC模式网站上用户发布的信息,在自己的产品或服务中发布、使用该信息,则有较大风险构成不正当竞争。

案例还是建议各位看一看,担心自己写的爬虫违法,可以对号入座看一看,总结下爬的基本操守:

  • 先确定爬啥网站:国家事务,国防建设、尖端科学技术领域的不要碰;
  • 确定什么内容:个人隐私、个人信息、商业秘密不要碰;著作权法保护、不正当竞争保护的数据,自己偷着乐,不传播和用作盈利还好 (如数据分析参考下~)。
  • 爬取手段:温柔点,尽量别影响正常用户的使用,细水长流,把别人网站搞挂了,不搞你才怪。
  • robots协议:em...我是小人

天网恢恢,疏而不漏~

好吧,侃这一Part就到这里,讲了:自己接触Python的历程 + 很火的Python课广告解读 + 爬虫违法的范畴解读,相信可以打消一部分想学Python爬虫的小白萌新心中的顾虑。前戏有点多了,立马开始正文Python爬虫入门秀。


0x2、开胃小菜——学点Python基础

既然是学Python爬虫,肯定是要学点Python基础的,主要原因:

  • 可以让你把为数不多的精力花在爬虫学习上,而不是花在解低级的语法错误上;
  • 某些基础知识是Python爬虫的前置知识,如文件操作;

一个误区

要把Python基础语法学得很完整才敢往下学Python爬虫,先不说这得何年何月,Python爬虫中涉及的Python基础语法都是比较简单的。高深语法,以后进阶再考虑,或者用到再查也不急,如并发相关。

一个建议

学习语法时做好笔记,大部分是API用法,心里有个印象,以后碰到在回来查就好!

《Python基础》 肝完了,读者可以跟着笔者的思维导图走一波,也可以自行搜索资料学习,笔者把基础部分划分为11个模块

  • Python开发环境搭建
  • 注释与模块
  • 基础常识
  • 变量、常量和字符串
  • 数据类型
  • 条件判断与循环
  • 函数
  • 异常与断言
  • 类与对象
  • 文件存储
  • 常用模块

具体章节脑图如下:


0x3、爬虫缘起——爬小姐姐

很多人学爬虫,都是从爬小姐姐开始的,笔者也不例外,毕竟 爱美之心,人皆有之。

《Python爬虫》 就肝了一点,就没有详细的思维导图咯,更多的只是 讲解思路,读者可自行搜索关键词学习,网上爬虫相关的文章泛滥,基本都能找到,是在找不到的可在评论区留言。


① 概念

1) 引出爬虫

比如,你最近想找一些美女壁纸作为电脑壁纸,然后发现了一个网站,上面有很多符合你口味的图片,如:

疯狂 打开图片详情,跳转到详情页,然后右键保存,毕竟只有小孩子才做选择,而你:

在保存了十来张后,你渐感有点憋不住,开始萌生这样的想法:

能不能整个东西,代替你做这些重复操作,让你的双手从键盘和鼠标上腾出来,做点其它的事情。

还真有,爬虫 就是这样一种东西:

按照特定规则,自动浏览或抓取万维网信息的程序或脚本。

除了支持这种网页自动化操作外,爬虫还可以用作数据爬取,有个误区要注意下:

不止Python能写爬虫,其他语言也支持,如JavaScript、Java和PHP等,只是刚好笔者会这个而已。

2) 基础知识

为了解放你的双手,你决定开始学习编写爬虫,但在此之前,你还得再 了解 一些Web的基础常识:

HTTP协议

  • URL的组成
  • HTTP请求报文 (请求行、请求头、请求正文)
  • HTTP响应报文 (状态行、响应头、响应正文)
  • Session和Cookie

Web基础

  • HTML:超文本标记语言,决定网页结构与内容,简单了解下标签语法;
  • CSS:层叠样式表,决定网页的表现形式,简单了解下三类CSS选择器(标签、类、id);
  • JavaScript:简称JS,一门脚本语言,决定网页行为,知道下就好,学js破解时再学习也可以;

② 爬虫初体验

对Web常识有个基本认知就可以往下学了,爬虫的编写一般分为四步:抓包模拟请求数据解析数据保存

1) 抓包 → Chrome开发者工具

Chrome浏览器自带,Windows按F12可以直接打开,切换到Network选项卡,F5刷新网页即可开始抓包:

可对特定类型的请求进行过滤,或者搜索自己所需的请求,这里要注意一点:

有些网页使用js动态渲染页面,Elements 是渲染后的网页结构,大部分模拟请求的库,是不支持js动态加载的。所以有时会出现 Elements 上有的结点,模拟请求的时候却拿不到,所以要以NetworkSources部分的请求响应结果为准!!!


2) 模拟请求 → requests库

抓包定位想爬的链接,接着就到模拟请求了,很多老教程会提下 urllib 库,在我看来,它的唯一优点就是 内置,不用另外导包,但真的不好用!建议直接上 requests 库,简单易用不是一点点。

拼装请求内容 前要对请求进行分析,流程如下:

  • 请求类型:GET、POST还是其他;
  • URL拼接规则:\xxx\yyy 类型就直接拼接,?xxx=yyy&zzz=ooo就传参 params
  • 请求参数:一般POST方式才有,直接复制粘贴,塞字典里,传参 data
  • 请求头:看Request Headers,一般最少要传User-Agent、Host,其他的看着粘贴,传参 headers;

解析完,就到拼装请求内容模拟请求的环节了,一个简单的代码示例如下:

import requests as req

base_url = "https://xxx.xxx"
page_url = base_url + "/yyy"    # 请求URL

# 请求头
page_headers = {
    'Host': 'xxx.xxx',
    'Referer': base_url,
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '
                  'Chrome/83.0.4103.97 Safari/537.36',
}

# 请求参数
page_data = {'id': 123}


def fetch_page():
    try:
        # POST请求类型
        resp = req.post(page_url, headers=page_headers, data=page_data)
        print("请求:", resp.url)
        if resp is not None:
            if resp.status_code == 200:
                # 解析返回的数据


    

                print(resp.content)
    except Exception as e:
        print(e)


if __name__ == '__main__':
    fetch_page()
复制代码

模拟请求,如果爬取结果与预期不符(内容不对或报错),排除程序本身的逻辑错误,常见的排查方向如下(*标注为不常见):

  • 1、请求链接是否正确 → 打印出来跟目标链接对比一下;
  • 2、重定向问题 → requests库默认自动处理重定向,请求前后打印的url不一致,可能是这个问题,可以添加参数 allow_redirects=False 禁用重定向,重定向链接一般在响应头的 Location 字段,也可以打印 history 跟踪重定向历史;
  • 3、请求头/请求参数是否漏传或误传,如有些站点要Referer(反盗链用),有些又不要,传了还可能报错;
  • 4、请求类型错误,有些接口只支持POST,不支持GET;
  • 5、请求次数过于频繁,有些站点限制了一段时间内某个IP或用户访问的频次;
  • 6、网页数据是js动态加载的
  • 7.*后端不走寻常路,如post请求参数包含中文,网站编码gb2312,需要对中文参数encode("GB2312")下等。
  • 8、Cookies问题:设置错或没设Cookies,简单的可使用Session(会话)自动处理Cookies;
  • 9、SSL证书验证问题:爬取站点不被CA认证的HTTPS证书,可能会报SSL错误,可添加参数 verify=False 跳过验证,配合 urllib3.disable_warnings() 把857警告也去掉;

以上就是笔者大概的排查过程,欢迎评论区补充,第5、6点解决方法会在反爬虫那里进行讲解。


3) 数据解析 → lxml库

模拟请求得到目标数据后,就要进行下一步了数据解析了,一般原始数据有三类:HTML、XML和JSON。

JSON用自带的**json库** 解析即可,Python基础的常用模块部分有讲解,另外两种可以用 lxml库 解析。

lxml 库,底层由C语言实现,支持XML和HTML的解析,使用 XPath表达式 定位结点,解析效率非常高。

库本身用法非常简单,如:

import requests as req
from lxml import etree

resp = req.get("https://www.baidu.com")

# 传入html文本,得到一个可进行xpath的对象
selector = etree.HTML(resp.text)

# 编写提取图标路径的XPath表达式
xpath_reg = '/html/head/link[@rel="icon"]/@href'

# 提取结点
results = selector.xpath(xpath_reg)

# 打印结点信息
print(results[0])
复制代码

难的是 XPath语法,语法有:

  • 结点关系:父、子、同胞、先辈、后代
  • 选取结点:最有用、谓语、选取未知节点、选取若干路径、轴、运算符、函数

XPath的完整教程可参见:《XPath 教程》

Tips:调试小技巧,Chrome Elements选项卡,按下Ctrl+F,可在此直接验证XPath表达式是否能正确定位。还可以右键目标结点,Copy → Copy XPath直接获取XPath表达式,当然这种方式获取的XPath表达式一般不是最优的。


4) 数据存储 → 保存为二进制文件

利用lxml解析到了目标数据,接着就到数据存储了,目标数据可能是:图片、音视频、普通文本 等,普通文本(如小说)直接保存txt,其他文件无脑保存成二进制文件,示例如下:

with open('out.txt', 'w+') as f:
    f.write(resp.content)

# 二进制文件把文件模式从w+改成b+就好~
复制代码

图片、音视频类可能还是一个资源链接,还需对这个链接进行模拟请求,才能获取到目标资源。

以上就是爬取一个简单网站的基本流程,读者可以找个简单壁纸/小说网站试试水,真的超简单~


③ 爬虫进阶

经过初体验,爬取简单网站,保存数据已不是什么难事,往下走就是针对各个方面的进阶了。


1) 抓包进阶

Chrome开发者工具抓包一般是够用的,还可以在上面进行js逆向,不过学点其他工具的使用也无妨。

两个最常见的PC抓包工具:Fidder(Windows建议) 和 Charles(Mac建议),原理都是 中间人代理,除了能抓浏览器的数据包外,还能抓PC、移动端的数据包,除此之外还有模拟请求等功能。两者用法和功能类似,二选一学习即可。

有时网页端或PC端不好分析,还可以尝试从移动端入手(手机、平板),上述两个抓包工具都可以抓移动端的请求(此处以Android为例 ):

  • 对于 普通的HTTP请求,手机Wifi和PC在一个局域网,抓包软件 打开允许远程发送请求、查看本机ip,然后手机设置下代理,就可以开始抓包了,如:

  • 而对于 HTTPS请求,则需要安装CA证书,以Charles为例:

浏览器访问下述链接下载安装证书:

  • 安装完证书能抓到HTTPS请求了,但是看不到请求内容,那可能是因为:

Android 7.0(Nougat,牛轧糖)开始,Android更改了对用户安装证书的默认信任行为,应用程序「只信任系统级别的CA

解决方法,要么 用Android 7.0以前的设备,要么 手机root,把证书安装到系统证书中,具体操作步骤如下:

# 打开终端,输入下述命令把 cer或者der 证书转换为pem格式
openssl x509 -inform der -in xxx.cer -out xxx.pem

# 证书重命名,命名规则为:<Certificate_Hash>.<Number>
# Certificate_Hash:表示证书文件的hash值
# Number:为了防止证书文件的hash值一致而增加的后缀

# 通过下述命令计算出hash值  
openssl x509 -subject_hash_old -in Fiddler.pem |head -1

# 重命名,hash值是上面命令执行输出的hash值,比如269953fb
mv Fiddler.pem <hash>.0

# adb push命令把文件复制到内部存储
adb push <hash>.0 /sdcard/

adb shell   # 启动命令行交互
su          # 获得超级用户权限
mount -o remount,rw /system   # 将/system目录重新挂载为可读写
cp /sdcard/<hash>.0 /system/etc/security/cacerts/  # 把文件复制过去
cd /system/etc/security/cacerts/    # 来到目录下
chmod 644 <hash>.0    # 修改文件权限

# 重启设备
adb reboot
复制代码
  • 如果将证书安装到系统后,还是抓不到HTTPS的话,可能是 APP设置了不走系统代理

Flutter 中默认不走系统代理,又比如APP使用了 OKHttp 请求网络,设置了proxy(Proxy.NO_PROXY)或重写了proxySelector;

传统的中间人代理方式就走不通了,可以用电脑作为热点,然后手机连接此热点,用 Wireshark 抓包:

Wireshark直接抓的是本机网卡的进出流量,能抓到所有流量包,支持一到七层全解码,缺点就是为了抓特定TCP Flow/Session 流量要写一个长长的过滤条件表达式,对初学者不怎么友好,不过对协议研究也特别有帮助。

  • 还有,部分应用为了防止中间人抓包,会进行证书认证,你一用代理,APP就不能正常使用,分为两种:
  • 单向认证:只在APP端做证书校验,破解之法也简单,hook ssl校验相关代码,如使用Xposed插件 JustTrustMe,对于okhttp混淆加密的可以用 JustTrustMePlus
  • 双向认证:APP端和服务端都对证书进行校验,破解之法就繁琐一些了,通过 Frida Hook脚本把证书抠出来,一般是Hook java.security.KeyStore类的load()方法,因为它的形参就是需要的证书和密码。把它们配到抓包工具中,就可以抓双向认证的包了。还有一种不用抠证书的,就是Hook SSL加解密的类,不解密APP上显示啥?在此将数据保存为pcap格式,再用Wireshark打开查看明文数据。

Android设备大概的抓包技巧就上面这些了,Frida相关脚本自己Github搜搜,挺多的,工具也不局限上面这些。

另外移动端也有一些抓包相关的工具:HttpCanary(手机版的中间人抓包)、tcpdump(需要root权限,导出数据包,用Wireshark等协议工具查看结果)、Drony(创建了一个VP嗯,将流量都重定向到自身,无视无代理模式抓包,需配合抓包工具如Charles使用,高版本好像失效了~)

苹果手机没用过,具体的抓包手段就不知道了~


2) 模拟请求进阶

requests库 模拟请求,也是基本够用的了,硬要挑毛病的话,有下述两个问题:

  • 目前 不支持 HTTP 2.0
  • 在不借助第三方库的情况下,只能发送 同步请求,即发起请求后堵塞,得等服务器响应或超时,才能进行下一个请求;

目前支持HTTP 2.0的Python模拟请求库貌似只有:httpxpython-hyper/h2

而同步请求效率不高的问题,解决方法有很多种:多线程、多进程、协程、替换成支持异步请求的库

笔者的理解(可能有误):Python中由于**GIL(全局解释锁)**的存在,一个线程运行其他线程堵塞,使得多线程代码不是同时执行的,而是交替执行。这种 伪多线程 的不足,其实针对的 计算密集型 的任务的不足,对于爬虫这类 IO密集型 的操作,使用多线程还是能提高效率的。

多线程简易示例如下:

import threading

def load_url(page):
    resp = req.post(page_url, headers=page_headers, data={"page": page})
    print(resp.text)


if __name__ == '__main__':
    page_list = [x for x in range(1, 100


    
)]  # 模拟生成100页
    # 根据爬取页面数生成对应数量的线程
    thread_list = [threading.Thread(target=load_url, args=(page,)) for page in page_list]  
    for t in thread_list:
        t.start()  # 启动线程
    for t in thread_list:
        t.join()  # 等待每个线程执行完毕
复制代码

当然,不建议使用这种直接创建一堆线程的情况,没有复用,可以引入线程池让其自行调度,简易示例如下:

from multiprocessing.pool import ThreadPool

if __name__ == '__main__':
    page_list = [x for x in range(1, 100)]  # 模拟生成100页
    pool = ThreadPool(10)
    for p in page_list:
        pool.apply_async(load_url, args=(p,))
    pool.close()
    pool.join()
复制代码

并发操作,结果写入,记得 加锁!!!不然会出现乱序的情况。多进程(Process)和进程池(Pool)实现也是类似,就不复述了,说下 协程

笔者的理解(可能有误):多线程和多进程任务切换存在开销,协程是定义了一个更小的操作单元,本质是单个线程,各种类型的任务被放到这个单线程里(IO或CPU密集计算),然后开发者可以自行调度任务的执行顺序,如遇到IO操作,切去执行其他任务,而不是堵塞。

Python 3.4 开始引入协程的概念(以生成器yield为基础),3.5 新增 asyncio库 作为Python标准库,使得协程的实现更加方便,其中最主要的是两个关键字:async(定义协程)await(挂起堵塞方法的执行),简单过下asyncio库的用法,先是几个概念:

  • coroutine → 协程对象,用async关键字修饰一个方法,调用此方法不会立即执行,而是返回一个协程对象,可将此对象注册到事件循环中,等待调用;
  • task → 任务对象,对coroutine的进一步封装,包含任务的各种状态;
  • future → 将来执行或没有执行的任务的结果,和task没有本质区别;
  • event_loop → 事件循环,将函数注册到这个循环上,当满足发生条件时,调用对应方法;

一个多任务协程的简单代码示例:

import asyncio
import random

# async定义协程方法
async def load_url(page):
    print("加载页面:", page)
    print("挂起页面:", page)
    # await堵塞(将耗时等待的操作挂起,转去执行其他协程,直到其他协程挂起或执行完毕)
    await asyncio.sleep(random.randint(0, 3))
    print("解析完页面:", page)


if __name__ == '__main__':
    cp_utils.is_dir_existed(out_dir)
    page_list = [x for x in range(0, 100)]  # 模拟生成100页
    coroutine_list = [load_url(x) for x in page_list]  # 构造任务,生成协程对象(coroutine)
    loop = asyncio.get_event_loop()  # 事件循环
    # run_until_complete将协程注册到事件循环loop中,然后启动
    loop.run_until_complete(asyncio.wait(coroutine_list))
    loop.close()
复制代码

还可以调用 ensure_future() 函数将写好协程对象转换为task对象,绑定一个任务完成的回调,在这个回调里可以通过task对象的result()即可获得返回结果,改动下代码:

# async定义协程方法
async def load_url(page):
    print("加载页面:", page)
    print("挂起页面:", page)
    # await堵塞(将耗时等待的操作挂起,转去执行其他协程,直到其他协程挂起或执行完毕)
    await asyncio.sleep(random.randint(0, 3))
    return "解析完页面:", page


def complete(t):
    print("Task status:", t.result())


if __name__ == '__main__':
    cp_utils.is_dir_existed(out_dir)
    page_list = [x for x in range(0, 100)]  # 模拟生成100页
    # 构造任务,利用ensure_future函数生成task对象
    task_list = [asyncio.ensure_future(load_url(x)) for x in page_list] 
    for task in task_list:
        task.add_done_callback(complete)    # 添加回调函数
    loop = asyncio.get_event_loop()  # 事件循环
    # run_until_complete将协程注册到事件循环loop中,然后启动
    loop.run_until_complete(asyncio.wait(task_list))
    loop.close()
    # 回调其实等同于下面直接拿
    for task in task_list:
        print(task.result())
复制代码

很简单是吧,不过要注意一点:

await 后面要跟着一个协程对象,将requests请求的方法抽取出来并用async修饰,看似会在请求时,服务器没响应挂起,切换到其他协程。实际上,并不会,requests会把asyncio堵塞住,还是同步!!!

一种解决方法是:利用 loop.run_in_executor() 将同步操作变成异步操作,示例如下:

async def load_page(page):
    temp_loop = asyncio.get_event_loop()
    result = await temp_loop.run_in_executor(None, req.post, page_url, {'page': page})
    return result.text  # 获取响应文本
复制代码

不过传参有些麻烦,比如requests.post(),没有设置headers关键词参数,无法直接传,需要自己构造Requests实例然后通过Session实例的prepare_request()方法发起一个请求。所以建议还是使用支持异步操作请求方式的库:aiohttp,基于asyncio实现的异步HTTP框架,分为客户端和服务端量部分,我们用前者就够了,修改下上面请求的代码:

async def load_page(page):
    async with aiohttp.request('POST', page_url, data={'page': page}) as resp:
        body_text = await resp.text()
        print(body_text)
复制代码

具体详细用法可参见官方文档:Welcome to AIOHTTP,除了aiohttp还有很多支持异步请求的网络库,如 httpx,篇幅限制就不在这里一一介绍了。

上面的多线程,多进程、协程、更换异步请求库提高爬取效率的手段,本质上还是在一台机子上跑爬虫程序,受本机的带宽、硬件影响。对于一些大型的爬取任务,还可以通过 分布式爬虫 提高爬取效率。


3) 数据解析进阶

初体验部分学了基于 XPath语法 解析html或xml的 lxml库,一笔带过其他两个常用解析库:

  • BeautifulSoup库:简单易用,需要解析器(安利lxml),支持结点选择,文档数搜索,大部分CSS选择器;
  • PyQuery库:API与jQuery类似,支持大部分CSS选择器,支持DOM操作(增删结点);

解析速度:lxml > PyQuery > BeautifulSoup,类似的解析库网上有很多,核心还是得掌握 XPath语法CSS选择器语法

此Part重点还是说下字符串提取神器——正则表达式,很多人对其都是望而生畏,其实不然,掌握正确语法+练习,写个简单提取目标字符串的正则还真不难。

Python 内置 re模块 处理正则表达式,提供了下述函数:

  • 匹配(找一个):match() → 从开头进行匹配、search() → 扫描整个字符串;
  • 检索与替换(找多个):findall() → 扫描整个字符串所有能匹配的、finditer()、split() → 分割;
  • 编译Pattern对象:多次调用相同正则,将正则表达式编译成Patter对象复用;
  • 分组:group() → 获得分组匹配内容,如group(1) 获得第一个匹配分组;

然后调用这些函数可以传入一个flags参数(标志位修饰符),来控制匹配模式,可使用多个,用|进行连接:

re模块的API就这么简单,接着是正则表达式的语法:(字符数量边界分组断言贪婪与非贪婪)

所谓的断言就是:给指定位置加一个限定条件,规定此位置前/后的字符需满足条件才算匹配成功:

正则匹配默认是 贪婪匹配,就是匹配尽可能多的字符,示例如下:

是的,语法也是那么简单,多在爬取过程中尝试练习吧,从无脑**(.*?)**开始慢慢优化。另外,还有两点要注意下:

  • 需要匹配特殊字符串时,可在前面加反斜杠进行转义,如**\\(**;
  • 想避免多次转义造成的反斜杠困扰,可在正则表达式前加上**r修饰,告知编译器它是原始字符串**,不要转义反斜杠;


4) 数据存储进阶

初体验部分直接把文本保存成txt,其他保存成二进制文件,有些粗暴了,实际上还需要对数据进行细分。

类似于爬取豆瓣音乐Top250,数据是这样的:歌名-作者-发行日期-分类-评分-评分人数,可以用:csvExcel 保存。对应的库:csv库xlwt/xlrd库

爬取贝壳网房源、拉钩职位等也是类似,配合数据分析基础三件套(库):pandas + numpy + matplotlib ,做下简单的数据分析及可视化,也是挺有趣的。

上述这种把数据保存成csv或Excel的形式还有一个好处,查看数据的成本低,没有编程的人也能直接看,没学习成本,不需要额外安装软件(手机或PC一般内置)。

除此之外,一种适合开发者的保存方式是把数据保存到 数据库 中,数据库一般分为两类:

  • 关系型数据库:通过二维表保存,存储行列组成的表,通过表与表的关联关系来体现;
  • 非关系型数据库,又称NoSQL,基于键值对,不需要经过SQL层解析,数据间没有耦合,性能非常好;

关系型数据库用 MySQL 比较多,得掌握点基础:

MySQL安装配置、数据库基本语法(增删改查)、Python连接操作数据库(如pymysql库)、特殊符号与表情存储问题、数据库可视化工具使用(如NavicatDataGrip)

其次是非关系型数据库,常用的有 MongoDB (文档型数据库) 和 Redis (键值对型数据库):

MongoDB,由C++编写,基于分布式文件存储,灵活的文档模型,JSON格式存储最接近真实对象模型,笔者经常把爬取到的JSON数据直接塞里面,后续再对数据进行清洗,得掌握点基础:

MongoDB安装、Python连接操作数据库(如**PyMongo库**)

Redis,用ANSI C编写,支持网络,基于内存、可选持久化、Key-Value的NoSQL,速度快,分布式爬虫常常用来保存任务队列,得掌握点基础:

Redis安装配置、Python连接操作数据库(如**redis-py库**)

对了,有一些资源是没办法直接拿到的,可以利用一些第三方库来获取,如爬取油管、B站视频可以用 you-get库 解析下载。又比如一些提供报告的站点,不提供直接的报告PDF,而是把报告的每一页作为图片展示出来,如果想整合成一份PDF,就要自行处理了:把图片下下来,pillow库 去下alpha通道,然后用图片生成PDF的 img2pdf库 合成。

另外,不是一定就得把资源Download下来或者传CDN,占空间,可以只保存一个链接,偶尔看到有些盗版漫画APP的图片地址就是指向原站点地址的,2333,防盗链处理也简单,客户端请求图片带上原站点Referer就好了(一般)~

还有,CDN的资源,一般 是不限制IP访问频次的,所以爬取的时候可以先存链接,等爬取完再批量下载~


④ 简单反反爬虫

反爬虫就是站点通过一些技术手段,提高爬虫的爬取门槛,反爬的奇淫巧技有很多,笔者不是专门做这个的,所以只会一些简单的反反爬虫技巧,欢迎在评论区补充~

1) 未登录无法访问

登录态 的保持和验证一般是 Cookies,有些还会配合一个动态变化的token,未登录无法访问,那就保持登录态哇~

  • 简单不变的Cookies,浏览器登录了,抓下包,复制下Cookies,模拟访问的时请求头带上;
  • 抓下登录的接口,比较简单的话,就先模拟登录下,配合requests的Session,后续会话都是登录态;

还可以把Cookies序列化到本地,爬虫重新运行时反序列化设置下。而对于复杂且一直变化的Cookies,上述两种方法可能就不可行了,需要自己破解构造规则,或者放弃抓包,用自动化测试工具爬取。


2) 页面使用Ajax异步加载

常见于 分页 的网页,requests模拟访问原始页面链接只能拿到局部数据,原始页面滑动到底部会加载更多数据。

应对之法直接爬这个Ajax数据接口,Chrome开发这工具直接过滤 XHR 类型请求,然后看请求参数,一般跟分页相关的(如第几页page、条数limit等),看响应结果,一般是能拿到总记录数的,自己换算下,自己本地构造一个参数列表,遍历传参模拟访问Ajax接口,即可得到所有数据。


3) 锁IP

User-Agent 这个常识就不用说了吧,服务端用来识别是否为合理真实的浏览器,请求头带上,可以从 fake-useragent库 里随便拿一个。

某个IP在一段时间内访问次数过于频繁,超过了服务端设置的阈值,就会被认定为爬虫进行封禁,后果就是,本机(或者整个局域网) 突然访问不了该站点。

一般过段时间就会解禁,但也会进入一个观察名单,更严格的访问频次限制。

应对之法使用代理IP,代理IP常见的获取渠道有下面这些:

  • 免费代理:浪费时间,都被用烂了,基本秒失效,可用性极低,如西刺;
  • 付费代理:一分钱一分货,有些便宜的,还得自己写个 代理池 筛一筛,个人习惯使用XX云的 代理隧道
  • ADSL:就是ADSL拨号上网,每次拨号都会换一个IP,上网租台动态拨号的 VPS主机。不建议把爬虫脚本托管到主机上(在上面爬站点),建议只把它作为一个 代理服务器(就是转发请求和响应),要把拨号服务器变成HTTP代理服务器可使用 SquidTinyProxy

注:ADSL拨号时,之前的IP会失效,可通过购买多个服务器来规避,自己的云服务器维护一个可用IP的队列,如用Redis存,key为每台拨号服务器,值为当前可用IP,暴露一个更新接口给拨号服务器调用。拨号服务器编写定时拨号脚本,拨号后调用此接口更新Redis里旧IP,定时的间隔根据拨号服务器的数量权衡,保证随时都有至少一台代理服务器能用。

对了,顺带提提代理IP根据隐藏级别的分类:

  • 透明代理:改动了数据包,还会告诉服务端你的真实IP;
  • 普通代理:改了了数据包,服务端有可能知道你用了代理IP,也有可能追查到你的真实IP;
  • 高匿代理:不改动数据包转发,服务端会认为是一个普通用户端访问,记录的是代理IP;

对了,不要想着用高匿代理就能为所欲为了,还是有办法溯源到你的真实IP的!不信可以 蒙古上单冲塔,快速并发访问下"牢狱之灾"提到的网站试试看,可能没过多久就有人请你去喝茶了~


4) 锁账号

跟锁IP是类似的,就是限制了一个账号的频次,应对之法:准备一堆账号,维护一个Cookies池。

这个也别想得太简单,有些朋友想着通过某些渠道购买一堆手机号码,批量注册,然后直接批量爬取,又送塔,别当服务端的风控是憨憨啊。

这种是最容易发现的了,一般都会有一个 养号 的过程,分时段注册(提前一段时间注册),然后每天做下日常(访问接口或模拟用户行为),让服务端觉得它是一个正常用户(所以,你会发现掘金经常有些不认识的"小号"给你点赞)。


?5) JavaScript

Tips:js破解没咋研究,没啥经验,我一般碰到这种就直接上自动化测试工具模拟了,后面有弄再回来补充吧...

某些接口的参数是一串看不懂的长字符串,且一直变化的,基本就是js加密;还有一些页面的数据加载也是通过js加密的(如大众点评字体反爬)。

客户端加密逻辑由js实现,源代码对用户是完全可见的,所以还会对js下述操作:

  • 压缩:去除不必要的空格、换行等内容、把一些可能公用的代码仅限处理实现分享,最后压缩成几行,使得代码可读性变得很差;
  • 混淆:使得js更难阅读和分析,有:变量混淆、字符串混淆、属性加密、控制流平坦化、僵尸代码、调试保护、多态编译、锁定域名、反格式化特殊编码等;
  • 加密:将一些核心逻辑使用诸如C/C++语言来编写,并通过JavaScript调用执行,从而起到二进制级别的防护作用;

6) 自动化测试工具模拟

如题,通过自动化测试工具,驱动浏览器执行特定动作,如点击、输入文本、滚动等,模拟人的操作的同时还能获取当前呈现的网页源代码(Elements)。

常用的两个库 SeleniumPyppeteer,后者依赖Chromium内核,无需繁琐的环境配置,相比起前者效率也高一些。网上教程教程烂大街了,就不详细介绍了。提两点:

  • 如果想拦截浏览器加载页面时发起的请求,可以配合其他抓包工具使用,如 mitmproxybrowsermob-proxy 等;
  • 使用这两个库模拟浏览器访问,有很多特征是可以被站点通过JavaScript探测到的;

服务端检测到浏览器行为不对劲,怀疑是"假人"在访问,就会触发验证手段,最常见的就是各种类型的验证码:

  • 简单图片验证码:OCR识别,tesseract自己采集图片训练,或者使用第三方OCR;
  • 复杂图片验证码:打码平台;
  • 滑动验证码:分析滑块和缺口图片的规律,计算出两者的距离,用Selenium模拟滑动;

验证码一直在变,倒立文字、滑动转圈等,但终究是敌不过 人工打码,yyds


7) 移动端爬取

在抓包进阶处提到 抓取手机数据包,操作起来还是有些麻烦的,可以用另一种变通的爬取手段 → 直接提取页面内容,就跟Selenium操作网页一样。

常用的两个工具:Appiumairtest,后者API更易用,基于图像识别技术定位UI元素 (Appium对混合开发页面无法直接定位),教程很多,不展开讲。

再往下走就是 APP逆向 范畴了,脱壳反编译APK源码,直接破解出加密规则,直接脚本模拟请求,或者直接写Xposed插件,对数据加载部分代码进行Hook,直接导出数据或将数据提交到自己的服务器等。


⑤ 爬虫框架

手撕爬虫久了,会发现写了很多重复冗余的代码,每次要爬个东西,都是复制拷贝之前的代码,然后改改改。

一种优化方案是抽取常用代码块封装,弄一个自己用起来趁手的模块。

另一种则是使用爬虫框架,其中包含了爬取的全部流程、异常处理和任务调度等,常用的两个爬虫框架 ScrapyPySpider,此处以基于事件驱动网络框架Twisted 编写的Scrapy为例讲解(官方文档),笔者的学习路线:

了解架构由哪些模块组成 → 模块各自的功能 → 模块间的协作 → 各个模块的定制;

Scrapy架构图

模块功能解读

  • Engine引擎,框架核心,控制数据流在框架内部的各组件间流转,在相应栋座发生时触发相关事件;
  • Scheduler调度器,请求调度,从引擎接收Request,添加到队列中,在后续引擎请求它们时提供给它;
  • Downloader下载器,获取页面数据并提供给引擎,然后提供给爬虫;
  • Downloader Middlewares下载中间件,下载器与引擎间的特定钩子,可在下载器把Responses传递给引擎前做一些处理;
  • Spider爬虫,产生Request,交给下载器下载后返回Response,解析提取出Item或需要另外跟进的URL类。
  • Spider Middlewares爬虫中间件,爬虫与引擎间的特定钩子,可在爬虫的输入和输出两个阶段做一些处理;
  • Item Pipeline项目管道,用于处理爬虫产生的Item,进行一些加工,如数据清洗、验证和持久化;

模块间的协作

一直重复上述的流程,直到调度器中不存在任何Request时,程序才会停止,对于下载失败的URL,Scrapy也会重新下载。

每个模块的订制就不展开讲了,网上很多,只提下对接Selenium对接HTTP接口调度库免去命令行启动

Scrapy同样不具有执行js的能力,所以对动态渲染的页面也是无能为力,可以对接Selenium来处理。对接方法很简单(Pyppeteer也是类似):

自定义下载中间件,在process_qequest()方法中对抓取的请求进行处理,启动浏览器进行页面渲染,再将渲染后的结果构造成一个HtmlResponse对象返回。

每次都启动Scrapy爬虫都要在命令行敲 scrapy crawl 爬虫名 ,如果是部署在服务器上,你还得连SSH,然后键入命令,比较繁琐,可以通过常用的Scrapy HTTP接口调度库来简化:ScrapyrtScrapyrd,前者更轻量,用法简单,不需要分布式多任务,可以直接用它实现远程Scrapy任务的调度。

pip install scrapyrt    # 安装
scrapyrt                # 在任意一个Scrapy项目运行如下命令即可启动HTTP服务
                        # 默认9080端口,也可以自行更换端口:scrapyrt -p 9081

# 接着就可以自行拼接请求了,如:
curl http://localhost:9080/crawl.json?spider_name=juejin&url=https://juejin.cn/

# GET请求支持参数如下:

spider_name:爬虫名称;
url:爬取链接;
callback: 回调函数名;
max_requests:最大请求数量;
start_requests:是否执行start_requests方法;

# POST请求Body可选配置参数如下:
spider_name:爬虫名称;
max_requests:最大请求数量;
request:JSON对象,支持Scrapy.Request类中所有的属性

# 返回结果是一个JSON格式的字符串,可自行解析判断~
复制代码


⑥ 分布式爬虫

上面的爬取任务都是在 一台主机 进行上爬取,现在扩展到 多台主机 上爬取,这就是分布式爬虫。一般爬取海量数据时才会用,如爬取知乎整站用户数据、微博所有用户信息、链接所有房产信息、电商商品价格监控等。

把原先的爬取任务列表,拆分到多台主机上,有两个问题要解决:

  • ① 怎么避免多台主机执行了同样的爬取任务,即 爬取任务的去重
  • ② 爬取中途有一台或多台主机抽风,爬取任务和数据怎么避免丢失?

问题②,好解,弄一个 共享爬取队列 就好,主机们都来这里拿任务,抽风前把任务又丢回队列里就好,至于数据丢失就更简单了,大家统一把爬取的数据都存到统一的数据库上就好了。

问题①,在共享爬取队列的基础上,对入队的任务做 唯一性校验,如Scarpy中就是弄了一个Request指纹,管理入队和出队,又得需要一个 调度器,还可以对任务优先级进行管理。

再接着,把 共享爬取队列调度器 都放到同一台主机上,然后其他主机只执行爬取任务,这就是 主从结构,其他的主机又叫 从机

共享爬取队列 涉及到并发访问,得找个支持并发的队列框架,常见的有:RedisRabbitMQCeleryKafka 等。

Redis就很好,set还支持去重,如果习惯用Scrapy写爬虫,不用自己实现,直接上 scrapy-redis,在Scrapy的基础上增加了Redis,还基于Redis的特性对组件进行了扩展:

  • Scheduler → 把Scrapy的queue换成Redis队列(集合变数据库),去重还是用的Request指纹,只不过用的Redis的set去重,每次从redis的request队列里根据优先级pop出一个request,发给spider处理。
  • Item Pipeline → 将爬取到的Item存入redis的item queue,修改过的Item Pipeline可以很方便地根据 key从items queue提取item,从而实现items processes集群(还能存到不同的数据库中,如MongoDb)。
  • Base Spider → 重写RedisSpider继承Spider和RedisMixin(从redis读url的类),继承这个RedisSpider生成的爬虫,调用setup_redis()获取连redis数据库,设置signals(信号)
  • 空闲时的signal,会调用spider_idle() → schedule_next_request() 保证爬虫一直是活的状态,且抛出DontCloseSpider异常;
  • 抓到Item的signal,会调用item_scraped() → schedule_next_request(),获取下一个request;

大概原理就是这样,然后是主机和从机的职责策略:

  • 主机(master):搭建Redis数据库,负责指纹判重、Request分配、数据存储;
  • 从机(slaver):负责执行爬虫程序,运行过程中提交新的Request给主机;

官方仓库 里example/project/example/spiders下有三个爬虫Demo类,改改就能用:

dmoz.py:继承CrawlSpider,只是用来说明Redis的持久性,把爬虫停了再运行,爬取记录还是保留在Redis里;

myspider_redis.py:继承RedisSpider,重写parse函数,支持分布式爬取,不再有start_urls,取而代之的是redis_key,即爬虫的启动命令,参考格式:redis_key = 'myspider:start_urls',根据指定格式,start_urls将从Master端的redis-cli里lpush到Redis数据库中,RedisSpider将从数据库里获取start_urls,执行方式如下:

  • Slaver端:scrapy runspider myspider_redis.py (爬虫处于等待状态)
  • Master端,在redis-cli中输入push指令,参考格式:lpush myspider:start_urls www.xxx.xx/
  • Slave端爬虫获取到请求,开始爬取;

mycrawler_redis.py,继承RedisCrawlSpider,支持分布式爬取,需遵守Rule规则,执行方式同上;

主从机最大的区别是settings.py文件的配置不同:

# === 主机配置 ===

# 使用scrapy-redis里的调度器组件,不使用scrapy中默认的调度器
SCHEDULER = "scrapy_redis.scheduler.Scheduler"

# 使用scrapy-redis里的去重组件,不使用scrapy中默认的去重
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
 
# 'scrapy_redis.pipelines.RedisPipeline 支持将数据存储到数据库中
ITEM_PIPELINES = {
    'example.pipelines.ExamplePipeline': 300,
    'scrapy_redis.pipelines.RedisPipeline': 400,
}

# 按优先级出列
SCHEDULER_QUEUE_CLASS = "scrapy_redis.queue.SpiderPriorityQueue"

# 主机IP和端口
REDIS_HOST = "localhost"
REDIS_PORT = 6379

# === 从机配置 ===
REDIS_URL = 'redis://root:xxx@master端IP:6379'
复制代码

基础的配置玩法就这样,读者可以试试,再往下走就是 分布式爬虫的部署与监控 ,也是我的技术盲区。

如何把爬虫部署到众多的云服务上正常运行,有两个问题要解决:

  • 环境配置:用到库和工具的安装(还要注意版本冲突问题),进行各种配置,SSH一个个敲,要哭;
  • 代码部署:爬虫脚本代码的更新,git pull一个个拉,要哭;

第一个问题,用 Docker 容器可以解决:

Docker 是基于Linux容器的封装,提供了简单易用的容器使用接口。而Linulx容器是一种虚拟化技术,不是模拟一个完整的系统,而是对进程进行隔离(进程外套一层)。使得进程访问到的各种资源都是虚拟的,从而达到与底层系统隔离的目的。可以理解为更轻量级的虚拟机,而且容器是进程级别的,相比虚拟机而言,启动更快、资源占用更少。

Docker镜像一般都会包含一个完整的操作系统,可以通过编写Dockerfile来定制Docker镜像。爬虫项目下新建Dockerfile文件(没有后缀),内容示例如下:

FROM python:3.6 # 使用Docker基础镜像,在此基础上运行Scrapy项目;
ENV PATH /user/local/bin:$PATH # 环境变量配置,将/user/local/bin:$PATH赋值给PATH
ADD . /code # 将本地代码放置到虚拟容器中,第一个参数代表当前路径,第二个参数代表虚拟容器中的路径;
WORKDIR /code   # 指定工作目录;
RUN pip3 install -r requirements.txt # 执行某些命令来做一些准备工作,如安装依赖库;
CMD scrapy crawl TestSpider # 容器启动命令,容器运行时会执行此命令,可以在这里启动爬虫;
复制代码

执行下镜像构建命令:docker build -t test:latest .,静待构建完毕,可以本地执行**docker run test** 测试下镜像。这个镜像文件可以直接拷贝到云服务器上载入,或者推到Docker Hub上,执行命令:docker run xxx/test,会自动下载和启动Docker镜像。

这Part就到这吧,没搞过,身边也没有可以问的人,看到有个不错的库:scrapydweb,感兴趣的可以试试康~


0x4、曲终人散——有缘再见

爆肝近一周,牺牲了很多摸鱼和打王者的时间,终于把Python爬虫入门内容串起来了,吐血...

是的,这些只是 入门姿势,笔者离进阶还差:js破解的经验积累大型分布式爬虫的部署实践自己设计开发一个爬虫框架,但也暂时先到这里了,算是还愿了吧,希望对想学Python爬虫的朋友有所帮助吧。

自学摸索过程中,都是看 青南崔庆才 两位大佬的文章过来的,站在 巨佬 的肩膀上,让我少走了很多弯路,受益匪浅,感恩~

参考文献

Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/117093