首页 / 资讯中心 / 文章详情

python实现精准搜索并提取网页核心内容

python实现精准搜索并提取网页核心内容 ★ FEATURED ARTICLE
前言标题里的「精准」二字需要先打个折扣。网页正文提取content extraction从原理上就是启发式的不可能 100% 准确。原因很简单HTML 只是排版语言它没有字段告诉机器「这段是正文、那段是广告」。一个div可以是文章主体也可以是侧边栏的推荐位语法上完全一样。所有号称「精准」的方案本质都是靠一组特征去猜猜得比其他方案准而已。理解了这一点后面的技术选择就顺了我们要的不是一个「绝对正确」的算法而是一个特征抓得准、阈值可调、失败时能降级的启发式流程。本文讲三类主流特征——语义标签、文本长度、链接密度——以及怎么把它们组合成一个可调参的提取器。需要说明本机没有 Python 解释器示例代码为逐行推演未经运行。「以官方文档为准」的地方会明确标注。另外提一句版本Python 2.7 已于 2020 年 1 月 1 日停止维护。Python 2 里str是字节串、unicode才是文本抓回来的 HTML 在老代码里常是字节串字符相关的判断比如标点计数会完全不一样。本文一律按 Python 3 写str就是文本。一、正文提取为什么必然不完美先给一个反例感受一下问题有多难。假设页面结构是div idmaindiv classarticle-bodyp第一段正文……/pp第二段正文……/p/divdiv classcommentp用户评论一……/pp用户评论二……/p/div/div如果只按「文本长度最大的块」评论多的时候评论区的文本长度会超过正文如果只按语义标签老站点根本没有article标签。没有单一特征能通吃这就是为什么成熟的提取库如可读性算法一类都采用多特征加权。因此本文的目标定成用一个打分函数把候选节点排序取分最高的当正文并允许调参。分数给得对不对决定了这套流程在实际站点上的表现。二、候选块从哪来提取的第一步不是选正文而是生成候选块。常见做法是遍历所有块级元素把它们当作候选。# 适用于 Python 3.8BLOCK_TAGS {article, section, div, main, td, body}def collect_candidates(root):收集块级元素作为候选正文容器。out []# iter() 递归遍历整棵树tag 为字符串的是普通元素for el in root.iter():if not isinstance(el.tag, str):continue # 跳过注释、处理指令等if el.tag in BLOCK_TAGS:out.append(el)return out这里用的是标准库xml.etree.ElementTree的接口习惯iter()、tag。若换成lxml.etreeiter()同样可用。先收集所有候选再统一打分比边遍历边判断要好调也方便事后打印分数排查问题。三、四类特征与打分3.1 特征一语义标签现代站点常给正文容器加语义标签或约定俗成的 id / class。命中就是强信号特征信号强度说明article标签强HTML5 语义标签直接表达「独立成篇的内容」main标签中表达「页面主体」但可能包含正文之外的区块id / class 含content、article、post中命名约定不同站点写法不一id / class 含nav、sidebar、comment、footer负命中的候选直接扣分语义信号的好处是准坏处是覆盖率低——老站点可能一个都不命中。所以它只能当加分项不能当唯一判据。3.2 特征二文本长度与标点密度正文的典型特征文本多而且标点密。导航链接虽然也占地方但通常没有句号、逗号、问号。可以用标点数量做一个粗略的语言性判断# 适用于 Python 3.8import rePUNCT re.compile(r[。、,.!?;:])def punctuation_score(text):标点越多越像自然语言正文纯链接列表的标点极少。return len(PUNCT.findall(text))findall返回匹配到的所有子串组成的列表取len即为标点个数。这个特征对中文尤其有效因为中文正文的标点密度很高。3.3 特征三链接密度关键链接密度 块内链接文本长度 / 块内总文本长度。正文里链接很少导航栏和列表页几乎全是链接。这条特征往往是区分正文与导航的决定性因素。# 适用于 Python 3.8def link_density(el):计算元素内的链接密度取值 0.0 ~ 1.0。total text_length(el)if total 0:return 0.0link_len sum(text_length(a) for a in el.iter(a))return link_len / totaldef text_length(el):该元素及其后代的可见文本总长度粗略把后代文本接起来。return len(.join(el.itertext()))itertext()会按文档顺序产出该元素下所有文本节点的内容把它拼接起来就是元素的纯文本。注意这是粗略的可见文本——它会把script、style里的内容也算进去生产代码需要先剔除。3.4 特征四段落数量正文由多个段落组成。统计块内p标签的数量是个简单但有效的正信号# 适用于 Python 3.8def paragraph_score(el):return sum(1 for _ in el.iter(p))四、加权打分与选择把上面的特征加权求和取最高分。权重是需要按站点调的经验值不存在普适的最优解# 适用于 Python 3.8NEGATIVE_HINTS (nav, sidebar, comment, footer, ad-, banner)POSITIVE_HINTS (content, article, post, main, detail)def score(el):text .join(el.itertext())s 0.0s punctuation_score(text) * 1.0 # 标点越多越像正文s paragraph_score(el) * 25.0 # 段落是强正信号s - link_density(el) * len(text) * 1.5 # 链接密度折算成扣分ident .join(filter(None, [el.get(id, ), el.get(class, )])).lower()if any(h in ident for h in NEGATIVE_HINTS):s - 1000.0 # 命中负面关键词重扣if any(h in ident for h in POSITIVE_HINTS):s 200.0return sdef extract_main(root):candidates collect_candidates(root)if not candidates:return Nonereturn max(candidates, keyscore)el.get(id, )取属性第二个参数是默认值属性不存在时返回它。max(..., keyscore)按分数取最大的候选。注意这个打分里的权重1.0、25.0、1.5是示意值不是经过实测标定的数字。真实项目里应当拿一批页面人工标注再回归调参而不是照抄。一个额外的健壮性设计如果最高分明显低于一个阈值说明整页可能都不是文章比如是列表页此时应当返回「提取失败」而不是硬返回一个错的块# 适用于 Python 3.8def extract_main_or_none(root, min_score100.0):best extract_main(root)if best is None or score(best) min_score:return Nonereturn best降级路径是正文提取系统的必备品宁可报告「没提到正文」也不要输出一段看起来像正文的导航。下游如果拿到垃圾还以为是正文错误会一路传下去。常见坑点以为存在 100% 精准的算法❌ 「找一个库调一次参数所有站点都能提到正文。」✅ 承认这是启发式问题为不同站点准备不同的权重配置并保留人工兜底。只按文本长度选最大块❌max(candidates, keylambda e: len(.join(e.itertext())))—— 评论、相关阅读多时必然选错。✅ 用「长度 标点 链接密度 段落」的组合分长度只占一项。忘了把导航和评论区扣分❌ 只加正分侧边栏因为堆了很多文字反而胜出。✅ 让 id / class 命中负面关键词的候选直接重扣把导航排除在竞争之外。用链接密度时没排除空文本❌link_len / total在total 0时抛ZeroDivisionError。✅ 先判断total 0返回0.0。把script和style的文本也算进长度❌itertext()会把内联脚本代码计入导致含大量 JS 的块被误判为正文。✅ 计算前先删除script/style子树。提取失败时硬返回一个块❌return max(...)无条件执行列表页也照样返回一个div。✅ 设最低分阈值低于阈值返回None让上层知道「这次没提出来」。忽略字符编码导致正文乱码❌ 用错误的编码解析响应正文里全是问号或方块标点检测随之失效。✅ 按响应头声明的编码解析缺失时再探测解析层编码错了后面所有特征都不准。总结特征作用局限语义标签article/main命中即强信号老站点覆盖率低标点密度判断「像不像自然语言」对短句、列表文案不敏感链接密度区分正文与导航的利器正文内链接多时会误伤段落数量简单有效的正信号长段落单段正文会吃亏负面关键词排除导航、评论、广告依赖命名约定正文提取的正确心态是「工程」而不是「定理」。先承认它必然不完美然后用多特征打分把准确率抬到一个可接受的水平再用最低分阈值给自己留一条失败的退路。把这三件事做好比追求一个不存在的「完美算法」实在得多。
阅读完成 · 觉得有帮助?
咨询建站