有时候,我们因为业务发展需要,需要重第三方数据平台获取一些数据,然后可能平台并没有提供比较友好的数据下载方式(可能你没有充值,哈哈),因此我们需要想一些办法来获取这些结构化的数据。
那么,想要达到我们的目的,我们大概有哪几种可能的方式呢?以下是我的一个浅显的思考,大概有以下几种方式可以达到我们的目的:
几种爬取数据的方式
- HTML信息解析
这种方式举个例子,最直接的理解,就是通过BeautifulSoup等库来解析HTML,从网页中提取你需要的结构化信息,你需要什么信息,自己去写匹配就好了。
ini
from bs4 import BeautifulSoup
import requests
# 请求网页
url = "http://example.com"
response = requests.get(url)
# 创建BeautifulSoup对象
soup = BeautifulSoup(response.text, 'html.parser')
# 找到所有的<a>标签
links = soup.find_all('a')
# 提取链接信息
for link in links:
print(link.get('href'))
- 使用Python写爬虫
这种一直就是很流行的方式了,写好爬虫几乎全自动化,代表作就是使用Scrapy框架,它提供了所有你需要编写网络爬虫的功能,例如请求处理、数据提取、数据处理和存储等。这里不过多赘述了,当然Python爬虫会比HTML信息解析要完善很多,这个可以自动去访问很多页,然后获取信息。
- 使用自动化测试框架
可能你没有见过这种方式,但是他的确是一个可行的方式,比如使用playwright自动化测试框架去获取网页的结构化信息。
ini
const playwright = require('playwright');
async function scrape() {
const browser = await playwright["chromium"].launch();
const context = await browser.newContext();
const page = await context.newPage();
await page.goto('http://example.com');
// 获取所有的<a>标签的href属性
const links = await page.$$eval('a', links => links.map(link => link.href));
console.log(links);
await browser.close();
}
scrape();
- 浏览器插件
这里我给一个代表作,webscraper[1] ,其背后的原理就是向网页中注入一个content.js,这个content.js干了啥事呢,他干的事情就是去做点击网页,去做结构化信息的收集,然后执行一些其他,然后汇总给到你导出。如图所示:
他的操作界面是chrome开发者工具下面的一个tab页面,你需要在里面配置一些规则,比如告诉他那些是link,哪些是你最终收集的数据字段等等。
api接口?
当然,最后不得不提一个的是,如果网页提供了api接口,你甚至可以直接通过重放和改写api接口请求参数的方式来抓取数据。但是对于靠数据吃饭的网站,你可能不要想得太美了。
几种方式爬取数据的弊端
虽然以上几种方式都具备获取你所需要的结构化信息的能力,但是各有利弊。
HTML结构化信息解析的方式可能只针对单页的情况,这种方式不提也罢,是最低级的方式,Python爬虫的方式相对来讲难度比较适中,但是如果需要处理登录态,处理图形验证等反爬措施时,就显得很难。
而,自动化测试框架也是不太容易处理图形验证码的情形。那么,对于最后的api接口的方式,有存在参数签名,这种方式的限制,哪怕是你改一点点参数,都会请求非法,签名算法通常是保密的,很难被破解。
爬虫还可能会被一种诱捕爬虫的技术所限制,比如网站会设置一些人类用户无法看见或无法访问的陷阱,如果爬虫访问了这些陷阱,那么它就会被识别为爬虫并被阻止。
所以,我们想要轻松拿到 晴来的web上的结构化信息,改何去何从呢?
真正的杀手锏
打开都知道,浏览器请求数据,底层无非是fetch和XMLHttpRequest的方式,因此我们难道不可以代理一下这两个底层的api吗?回答肯定是可以的:
我们说干就干,直接把下面代码复制到chrome console中尝试一下,你就知道效果了。
javascript
(function() {
'use strict';
// Intercept XMLHttpRequest
(function(open) {
XMLHttpRequest.prototype.open = function(method, url, async, user, pass) {
this.addEventListener('readystatechange', function() {
if(this.readyState === 4) { // DONE
console.log('XHR finished:', method, url, 'status:', this.status);
//log response:
console.log(this.responseText);
}
}, false);
open.call(this, method, url, async, user, pass);
};
})(XMLHttpRequest.prototype.open);
// Intercept fetch
(function(fetch) {
window.fetch = async (...args) => {
console.log('Fetch called with URL:', args[0]);
const response = await fetch(...args);
response.clone().text().then(text => {
console.log('Fetch response for URL:', args[0], 'Body:', text);
}).catch(err => console.error('Error reading fetch response:', err));
return response;
};
})(window.fetch);
})();
走出这么突破性的一步,就意味着我们可以捕获所有的请求了,那么,结合一个油猴,你应该就明白了,在web页上增加一个按钮,记录你所需要的数据回包,甚至进一步做一些数据处理,最终返回也不是什么难事了,如图所示:
代理底层XMLHttpRequest的好处
很明显,这种方式不存在之前咱们说的任何一种的弊端,有人可能会说,你这个不是需要手动点击吗,我想说的是,做自动点击难道不是添砖加瓦的事吗?可以说代理底层XMLHttpRequest方案唯一不能满足的场景可能是SSR方式的web,但是就目前前端技术栈来说,SSR通常不大会用于这种数据密集型站点,大多是一些产品主页,文档等。
ini
var links = document.querySelectorAll('a.myClass');
for (var i = 0; i < links.length; i++) {
links[i].click();
}
参考资料
[1]
webscraper:
www.webscraper.io/documentati...
探索代码的无限可能,与老码小张一起开启技术之旅。点关注,未来已来,每一步深入都不孤单。