某个周日晚上偶然发现了《火星异种》这部漫画,便在网上在线看了起来。在看的过程中图片加载很慢,而且有时候还不小心点到广告,大大延缓了我看的进度。后来想到能不能把先把漫画全部抓取到本地再去看。
经过一段时间的研究还是有所收获:成功在风之动漫网站上抓取了《火星异种》,以及在腾讯动漫上抓取了《海贼王》。一般来说,抓取网页内容有两种形式:一是静态资源,即网站内容在第一时间就全部呈现在网页上;另一种是动态资源,即网站内容随后通过用户交互,如:操作滚动条等,异步的更新。对于第一种形式,直接通过发送 request 请求并解析返回结果即可;对于第二种形式,可以通过 PhantomJS 模拟浏览器访问,并通过写代码模拟用户交互行为,最终拿到数据。
1. 静态资源抓取 (代码地址:Node.js、Python)
分析阶段:
1. 打开《火星异种》漫画首页 ,发现 “http://www.fzdm.com/manhua/47/” 有数字 “47”,将数字 “47” 改成 “48” 后发现是《吞噬人间》漫画首页,于是我们知道了这个数字用来标识不同漫画
2. 随机进入 48 话和 86 话,发现连接分别为“http://www.fzdm.com/manhua/47/48/” 和 “http://www.fzdm.com/manhua/47/86/”,于是我们知道了每一话的连接地址(注:实际上第001话的地址为 “http://www.fzdm.com/manhua/47/001/”,但由于当时已经看到 40 多话了,代码没有对这一块进行特殊处理)
3. 随机进入 50 话,随机查看不同页的 URL 后发现 URL 的形式为 “http://www.fzdm.com/manhua/47/50/index_*.html” 其中 * 和当前页码对应
4. 查看每一页的漫画图片元素发现,<img> 元素的 id = "mhpic",因此可以轻松获取到漫画图片的地址
5. 最后只需要发送请求到漫画图片所在的地址,并将图片数据保存在本地即可
注意事项:
1. 需要记录当前已抓取到哪一话,也就是记录当前抓取的状态。当需要抓取的内容特别长,不能一次性抓完时,保存上一次抓取的断点十分必要,同时也是为了在抓取的过程中出现错误后不用每次从头抓取
2. 最好在抓取之前先检测改文件是否存在,不存在才发送请求。这是为了当程序中出现bug导致已抓取的页面不全时,不用再重复抓取已经存在的图片
3. 添加重试机制,在一定时间内访问同一个站点次数太多,可能会出现服务拒绝之类的错误。调节好访问频率,出错后每隔一段时间进行重试即可。
Bonus:
在查看网页源码的时候发现,当前页面除了加载当前页的漫画图片资源外,还会加载下一话的图片资源,并在当前页隐藏。这样查看下一页的时候就能直接从缓存中读取数据。
2. 动态资源抓取 (代码地址:Node.js)
分析阶段:
1. 打开《海贼王》漫画首页,随机打开 857 和 858 话,地址分别为 “http://ac.qq.com/ComicView/index/id/505430/cid/874” 和 “http://ac.qq.com/ComicView/index/id/505430/cid/875”。发现虽然 URL 是连贯的,但和当前话数字对应不上。打开第一话发现地址是 “http://ac.qq.com/ComicView/index/id/505430/cid/1”,于是猜测中间有些 URL 可能失效了,需要再代码里做好容错 (无论在什么情况下最好都做好容错)。
2. 随机打开 857 话,发现漫画图片是随着往下滚动动态加载。查看网页源码发现,未加载漫画图片的地方是用地址为“http://ac.gtimg.com/media/images/pixel.gif”的图片占位。于是不能简单的通过单次请求来获取漫画图片地址。需要使用 PhantomJS PhantomJS for Node等工具来模拟浏览器访问,并通过代码模拟用户交互行为,最终获取数据
3. 通过 setTimeout 改变 scrollTop 来模拟下拉滚动条,滚动到页面底部后,查看页面有效 img 标签的数量看是否等于当前话的页数,否的话继续模拟用户从头下拉滚动条。
注意事项:
1. PhantomJS 代码是在隔离沙箱中运行,不能使用 Node 代码环境下的全局变量之类的,可以通过 “onConsoleMessage” 事件来与 Node 环境交互。
Python 和 Node 对比使用体验
由于对 Node 比较熟,所以是先写好 Node 版本,有时间的话在用 Python 写一遍。在写的过程中发现,Node 的回调写法用在这样的场景不太方便,但如果用上 ES7 的 Async/Await 的话,写起来就方便很多了;但用 Node 的优势是解析网页数据十分方便,就像在浏览器端进行 DOM 操作一样。使用 Python 的优势是同步的写法,缺点是解析网页数据不方便。
虽然 Node 的异步写法用在这种场景下很不方便,但也正因为异步的关系,可以用一个进程一次性抓取好几个不同的漫画,实现类似多线程的效果。