本文正在参加「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 上有的结点,模拟请求的时候却拿不到,所以要以Network或Sources部分的请求响应结果为准!!!
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模拟请求库貌似只有:httpx 和 python-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,数据是这样的:歌名-作者-发行日期-分类-评分-评分人数,可以用:csv 或 Excel 保存。对应的库:csv库
和 xlwt/xlrd库
。
爬取贝壳网房源、拉钩职位等也是类似,配合数据分析基础三件套(库):pandas
+ numpy
+ matplotlib
,做下简单的数据分析及可视化,也是挺有趣的。
上述这种把数据保存成csv或Excel的形式还有一个好处,查看数据的成本低,没有编程的人也能直接看,没学习成本,不需要额外安装软件(手机或PC一般内置)。
除此之外,一种适合开发者的保存方式是把数据保存到 数据库
中,数据库一般分为两类:
- 关系型数据库:通过二维表保存,存储行列组成的表,通过表与表的关联关系来体现;
- 非关系型数据库,又称NoSQL,基于键值对,不需要经过SQL层解析,数据间没有耦合,性能非常好;
关系型数据库用 MySQL
比较多,得掌握点基础:
MySQL安装配置、数据库基本语法(增删改查)、Python连接操作数据库(如pymysql库)、特殊符号与表情存储问题、数据库可视化工具使用(如Navicat、DataGrip)
其次是非关系型数据库,常用的有 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代理服务器可使用 Squid 或 TinyProxy。
注: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)。
常用的两个库 Selenium
和 Pyppeteer
,后者依赖Chromium内核,无需繁琐的环境配置,相比起前者效率也高一些。网上教程教程烂大街了,就不详细介绍了。提两点:
- 如果想拦截浏览器加载页面时发起的请求,可以配合其他抓包工具使用,如
mitmproxy
、browsermob-proxy
等; - 使用这两个库模拟浏览器访问,有很多特征是可以被站点通过JavaScript探测到的;
服务端检测到浏览器行为不对劲,怀疑是”假人”在访问,就会触发验证手段,最常见的就是各种类型的验证码:
- 简单图片验证码:OCR识别,tesseract自己采集图片训练,或者使用第三方OCR;
- 复杂图片验证码:打码平台;
- 滑动验证码:分析滑块和缺口图片的规律,计算出两者的距离,用Selenium模拟滑动;
验证码一直在变,倒立文字、滑动转圈等,但终究是敌不过 人工打码,yyds!
7) 移动端爬取
在抓包进阶处提到 抓取手机数据包,操作起来还是有些麻烦的,可以用另一种变通的爬取手段 → 直接提取页面内容,就跟Selenium操作网页一样。
常用的两个工具:Appium
和 airtest
,后者API更易用,基于图像识别技术定位UI元素 (Appium对混合开发页面无法直接定位),教程很多,不展开讲。
再往下走就是 APP逆向
范畴了,脱壳反编译APK源码,直接激活成功教程出加密规则,直接脚本模拟请求,或者直接写Xposed插件,对数据加载部分代码进行Hook,直接导出数据或将数据提交到自己的服务器等。
⑤ 爬虫框架
手撕爬虫久了,会发现写了很多重复冗余的代码,每次要爬个东西,都是复制拷贝之前的代码,然后改改改。
一种优化方案是抽取常用代码块封装,弄一个自己用起来趁手的模块。
另一种则是使用爬虫框架,其中包含了爬取的全部流程、异常处理和任务调度等,常用的两个爬虫框架 Scrapy
和 PySpider
,此处以基于事件驱动网络框架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接口调度库来简化:Scrapyrt
和 Scrapyrd
,前者更轻量,用法简单,不需要分布式多任务,可以直接用它实现远程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指纹,管理入队和出队,又得需要一个 调度器,还可以对任务优先级进行管理。
再接着,把 共享爬取队列 和 调度器 都放到同一台主机上,然后其他主机只执行爬取任务,这就是 主从结构,其他的主机又叫 从机。
共享爬取队列 涉及到并发访问,得找个支持并发的队列框架,常见的有:Redis、RabbitMQ、Celery、Kafka 等。
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 主题月分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/16014.html