记录一次失败的数据抓取之Puppeteer

前段时间女朋友需要统计一部分公司的办公地点,但由于公司非常多,自己一个一个查很明显不靠谱。于是就想是不是可以用什么方式抓数据呢?理所当然的想到了Python,可惜不会写,所以放弃了。再后来就在GitHub上看到了这个项目Puppeteer,做了一次数据抓取的尝试,本来想秀一把,结果秀的不好,翻车了,但好在见识到了Puppeteer的魅力。

介绍

Puppeteer is a Node library which provides a high-level API to control Chrome or Chromium over the DevTools Protocol. Puppeteer runs headless by default, but can be configured to run full (non-headless) Chrome or Chromium.

一言以概之就是chrome浏览器能干的事情,Puppeteer基本都能做到。

其它的抓取方式大部分可能是分析接口然后通过接口调用获取相应的数据,而Puppeteer是像人一样正常访问相应网站然后读取/修改/操作相应的网页内容。

简单总结了一下在我这次使用Puppeteer发现的优缺点:
优点:

  • 上手很容易
  • API足够的简单完善
  • 由于可以在访问、输入等方面做到拟人的操作,被检测的风险略低

缺点:

  • 由于和用户操作类似,需要加载网页相关资源,所以相对来说比较慢

准备工具

企业名称的excel
npm包:Puppeteer、node-xlsx
node环境:推荐高版本 Node 可以使用 async/await 语法
抓取对象:天x查

抓取分析

1.利用企业名称进行搜索
2.获取搜索列表第一条数据中的id
3.跳转到详情页
4.获取页面上详细地址一栏的数据

由于测试过程中发现,连续几次操作后进入详情页需要登陆,所以在最开始加入了登陆的流程。

抓取实现

有了上面的结论后可以使用 Puppeteer 进行模拟访问了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
const puppeteer = require('puppeteer');

(async () => {

// 打开浏览器以及一个页面
const browser = await puppeteer.launch();
const page = await browser.newPage();

// 跳转到登陆页
await page.goto('https://www.tianyancha.com/login');

// 模拟输入用户名和密码
await page.type('.modulein1 input.contactphone', '手机号');
await page.type('.modulein1 input.contactword', '密码');

// 模拟点击登陆
await page.click('.modulein1 .login_btn');
// 等待页面加载完成,以保证登陆成功
await page.waitForNavigation({
waitUntil: 'load'
});

try {
const address = await getDetailAddress('xx科技技术有限公司');
console.log('公司地址:',address);
} catch (e) {
// 异常时截图用于分析错误原因
await page.screenshot({path: "erroor.png"});
console.error(e);
}

// 获取公司地址
async function getDetailAddress(companyName) {
// 根据公司名搜索
await page.goto(`https://bj.tianyancha.com/search?key=${companyName}`, {
waitUntil: 'load'
});

// 获取搜索结果第一条的id
let detailIds = await page.evaluate(() => {
// 植入js代码到网页当中,并返回结果
return $('.search-result-single').eq(0).data('id');
});

// 判断id是否存在,决定是否进入详情页
if (detailIds) {
// 进入详情页
await page.goto(`https://www.tianyancha.com/company/${detailIds}`, {
waitUntil: 'load'
});

// 获取详情页的公司详细地址数据
let address = await page.evaluate(() => {
return $('.address').attr('title');
});

return address;
}

return;
}


})();

数据转换

从详细地址中提取出省市区街道信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
getAddressObj('北京市朝阳区阜通东大街1号院3号楼1单元2层111110')

function getAddressObj(addressStr) {
const arr = addressStr.match(/([^省]+省|.+自治区)?([^市]+市|.+自治州)([^县]+县|[^区]+区)([^区]+区|.+镇)?(.*)/);

if (!arr || !arr.length) {
return {}
}

return {
province: arr[1] || arr[2],
city: arr[2],
county: arr[3],
town: arr[4],
village: arr[5]
}
}

文件操作

对比了很久在node中操作excel比较好用的插件,最后还是选用了node-xlsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const fs = require('fs');
const xlsx = require('node-xlsx').default;


// =======读取excel数据=======
// 读取excel文件
const workSheetsFromFile = xlsx.parse(`${__dirname}/企业名称.xlsx`);
// 获取工作表中的所有数据
let allData = workSheetsFromFile[0].data;

// =======保存结果到excel=======
let buffer = xlsx.build([{name: "企业名称地区对照表", data: [/*数据*/]}]);
fs.writeFile(`企业名字对照表.xlsx`, buffer, {
encoding: 'utf8',
mode: 438,
flag: 'w'
}, function (err) {
console.error(err);
})

总结

最后,还是失败了,为什么呢?网站会在查询100多跳数据后提示输入验证码;整体速度太慢,一条数据抓取时间在在2s左右,要想抓取上万条数据耗时太长。

虽然是失败了,但还是尝试了一把 Puppeteer 感觉这东西可以在测试/抓取等方面提供很大的帮助。后面才知道有别的组大神用 Puppeteer 监控支付宝余额,玩儿的贼6。