完全使用浏览器扩展完成的爬虫

在给一个初学者讲爬虫的时候, 我为其演示了直接使用Chrome Console获取大部分数据, 之后打算顺着这条路走下去, 没想到坑真的是多. 但是话已经说下去了, 因而我必须用Chrome所有内置的功能完成一个爬虫. 遂得此篇.

最初我打算用Sources里的Snippets来做, 写完了才发现刷新了代码就会结束掉, 因此必须得保证代码能够持续进行.

一个很自然的想法就是使用window.open或者iframe, 但是由于索引页和目标页不在同一个子域名下, 因此除非跳转到其子域, 否则无法使用. 但是一旦跳转, 当前索引数据就会消失. 自然的想法是使用LocalStorage, 然而LocalStorage也有同源策略问题.

那么就只好写浏览器扩展了, 浏览器中content-script是注入到页面的, 但是只有一个这个也不行, 注入到页面的时候是在当前域名的空间下的, 因此还需要一个background来储存网页链接和爬取的数据.

所以文件结构为:

1
2
3
4
5
.
├── js
│ ├── background.js
│ └── content-script.js
└── manifest.json

但是事情怎么可能这么简单就过了呢? 由于页面加载需要时间, 循着用Python写爬虫的经验, 我随手写了一个

1
2
3
4
5
6
7
8
9
get_elem_by_xpath = (xpath) => {

elems = document.evaluate(xpath, document).iterateNext();
return elems? elems.textContent || elems: ''
}
// 强行Python命名风格

while(!get_elem_by_xpath(elem_xpath));

来等待元素出现, 该元素出现即网页加载完成. 然后Chrome就僵死了, 标签无法直接关闭, 控制台啥都看不到, 要关只能任务管理器.

我最初完全没有怀疑它的原因, 只是以为配置文件写错或者什么其他, de到最后才发现, 这行代码会把整个网页给带崩, 我初步怀疑document.evaluate是否可能代价太大, 以至于网页拿不到任何CPU资源来加载.

快要睡觉了才意识到content-script是在网页加载之处注入进去了, 因此就是网页内的事情, 单线程. 而我当时就像是魔怔一样想着二者是异步的. 于是我又尝试了阻塞等待时间, 简直在玩行为艺术.

意识到问题后, 就用setTimeout完成了异步递归, 大致就是:

1
2
3
4
5
6
7
8
f = (args..) => {
if(!get_elem_by_xpath(elem_xpath)){
setTimeout(f, ..);
} else {
..
}
}

而且其中比较有趣的是, location.href=...就和return差不多, 页面离开那么所有本页代码重置, 后面就不再运行.

background交互使用chrome.runtime.sendMessage, 那边接收使用chrome.runtime.onMessage.addListener, 监听器接受三个参数request, sender, sendResponse, 第三个用于返回数据. 此间可以直接传递JS对象, 不需要序列化和反序列化.

总的来讲, 整个流程就是:

  1. 如果当前是索引页, 到2, 如果是待爬页, 到5, 否则结束.
  2. 打开索引页, 程序获取urls, 发送给background暂存
  3. background获得urls, 如果urls为空, 结束, 否则到4
  4. url = urls.pop(), 把新的urls发送给给background记录, 之后跳转到url.
  5. 如果目标元素不存在, 1s后到5, 否则下一步.
  6. 获取元素信息并保存, 到3.

// 本篇采用yosoro书写