网站内容抓取(如何识别页面的核心内容?玩家和Python技术爱好者分享)
优采云 发布时间: 2021-12-12 23:17网站内容抓取(如何识别页面的核心内容?玩家和Python技术爱好者分享)
大家好,我是@无欢不散,资深互联网玩家,Python技术爱好者,喜欢分享硬核技术
欢迎关注我的专栏:
每个人都必须熟悉爬虫程序。随便写一个获取网页信息,甚至通过请求自动生成Python脚本[1]。
最近在网上遇到一个爬虫项目,需要爬取文章。感觉没什么特别的,但是问题是没有抓取范围的限制,也就是说没有清晰的页面结构。
对于一个页面来说,除了核心的文章内容,还有headers、tails、左右列表列等等。有的页框使用div布局,有的使用table。即使两者都使用div,less网站的样式和布局是不同的。
但问题必须解决。我想,既然搜索引擎已经抓取了各种网页的核心内容,我们应该也能应付。拿起 Python 去做吧!
各种尝试
如何解决?
生成PDF
开始想到一个比较棘手的方法,就是用一个工具(wkhtmltopdf[2])生成目标网页的PDF文件。
好处是不需要关心页面的具体形式,就像给页面拍照一样,文章结构就完整了。
虽然可以在源码级别检索PDF,但是生成PDF有很多缺点:
计算资源消耗大,效率低,错误率高,体积过大。
几万条数据已经两百多G了,如果数据量上来存储,那就是大问题了。
提取 文章 内容
在不生成PDF的情况下,有一种简单的方法可以通过xpath[3]提取页面上的所有文本。
但是内容会失去结构,可读性会很差。更可怕的是,网页上有很多不相关的内容,比如侧边栏、广告、相关链接等,也会被提取出来,影响内容的准确性。
为了保证一定的结构和识别核心内容,只能识别和提取文章部分的结构。像搜索引擎一样学习,就是想办法识别页面的核心内容。
我们知道,一般情况下,页面的核心内容(比如文章部分)文字比较集中,可以从这个地方开始分析。
所以我写了一段代码。我使用 Scrapy[4] 作为爬虫框架。这里只截取了提取文章部分的代码:
divs = response.xpath("body//div")
sel = None
maxvalue = 0
for d in divs:
ds = len(d.xpath(".//div"))
ps = len(d.xpath(".//p"))
value = ps - ds
if value > maxvalue:
sel = {
"node": d,
"value": value
}
maxvalue = value
print("".join(sel['node'].getall()))
简单明了,测试几页真的很好。
但是,在提取大量页面时,发现很多页面无法提取数据。仔细一看,发现有两种情况。
一些文章的内容是放在标签里的,所以我没有拿到每个文章
外面裹了一个
, 所以 p 的个数和 div 的偏移量
再次调整策略,不再区分div,查看所有元素。
另外,先选择更多的p,然后在此基础上看更少的div。调整后的代码如下:
divs = response.xpath("body//*")
sels = []
maxvalue = 0
for d in divs:
ds = len(d.xpath(".//div"))
ps = len(d.xpath(".//p"))
if ps >= maxvalue:
sel = {
"node": d,
"ps": ps,
"ds": ds
}
maxvalue = ps
sels.append(sel)
sels.sort(lambda x: x.ds)
sel = sels[0]
print("".join(sel['node'].getall()))
经过这次修改,确实在一定程度上弥补了之前的问题,但是引入了一个比较麻烦的问题。
发现的文章主体不稳定,特别容易受到其他部分的一些p的影响。
选最好的
由于不适合直接计算,需要重新设计算法。
发现文字集中的地方往往是文章的主体。前面的方法没有考虑这个,而是机械地找到最大的p。
还有一点,网页结构是一棵DOM树[6]
那么离p标签越近,越有可能成为文章的主题,也就是说离p越近的节点权重应该越大,离p越远的节点权重就越大p 时间,但权重也应该更小。
经过反复试验,最终代码如下:
def find(node, sel):
value = 0
for n in node.xpath("*"):
if n.xpath("local-name()").get() == "p":
t = "".join([s.strip() for s in (n.xpath('text()').getall() + n.xpath("*/text()").getall())])
value += len(t)
else:
value += find(n, a)*0.5
if value > sel["value"]:
sel["node"] = node
sel["value"] = value
return value
sel = {
'value': 0,
'node': None
}
find(response.xpath("body"), sel)
经过这次改造,效果特别好。
为什么?其实就是利用了密度原理,就是离中心越近的地方,密度就越大,而远离中心的地方,密度会呈指数下降,这样密度中心就可以被过滤掉了。
50%的斜率是怎么得到的?
其实是通过实验确定的。一开始,我把它设置为90%,但结果是body节点总是最好的,因为body收录了所有的文本内容。
经过反复实验,确定 50% 是一个更好的值。如果它不适合您的应用程序,您可以进行调整。
总结
在描述了我如何选择文章的方法后,我没有意识到它实际上是一个非常简单的方法。而这次解题的经历,让我感受到了数学的魅力。
我一直认为,只要理解了常规的处理问题的方式,应付日常的编程就足够了。当遇到不确定的问题,又没有办法提取出简单的问题模型时,常规思维显然是不行的。
因此,我们通常应该看看一些数学上很强的方法来解决不确定的问题,以提高我们的编程适应性,扩大我们的技能范围。
我希望这篇短文能对你有所启发。欢迎大家在留言区交流讨论,大展身手!
参考
[1] 卷曲到 Python:/
[2]wkhtmltopdf:/
[3]xpath:/xpath/xpath_syntax.asp
[4]Scrapy:/
[5]jQuery:
[6] DOM 树:/item/DOM%20Tree/6067246