抓取正文时踩到的三个坑:字符编码、图片地址拼接和空文本
上周给 HaoNews 的抓取流程做了一次正文抽取,目标是把抓回来的原始 HTML 变成干净的纯文本,写入本地一份约 12 万条资讯记录的索引。源站是海外站,编码混杂,有 UTF-8 也有少量 GBK 残留,单页正文长度从几百字到两三万字都有。跑完整批之后,发现三个问题必须单独处理。
第一个是编码。只要源站的 HTML 里没有声明 charset,或者声明了但和实际字节不符,直接 decode 出来就是一堆问号。我的做法是先拿原始字节,按优先级探测:
import chardet
def decode_bytes(raw: bytes) -> str:
for enc in ("utf-8", "gb18030"):
try:
return raw.decode(enc)
except UnicodeDecodeError:
continue
guess = chardet.detect(raw)["encoding"] or "utf-8"
return raw.decode(guess, errors="replace")
先试 UTF-8 和 GB18030 两个高概率项,失败再用 chardet 兜底。代价是 chardet 对大文件偏慢,所以我把探测结果按域名缓存,同一个站不重复探测。
第二个是相对地址。正文里的图片 src 大量是 ../images/xxx.jpg 这种,直接存进索引后链接是断的。我的处理是用 urllib.parse 按页面 URL 做绝对化:
from urllib.parse import urljoin
src_abs = urljoin(page_url, src)
这里踩过的坑是:如果页面里有 <base href>,得先读它,urljoin 要用 base 而不是 page_url,否则路径会错一层。
第三个是空文本。抽取器对某些正文包裹在多层 div 里的页面返回空字符串,但页面其实有内容。我的判断是:如果抽出来少于 50 个字符,就退回到整页可见文本,再用正则切掉导航和页脚。这个兜底有代价,会混入少量无关文本,但比整条记录丢失好。
边缘情况里最容易翻车的是 GBK 页面里夹着 UTF-8 的片段,逐字节解码会直接失败,errors=“replace” 会把那一段变成问号,事后很难补。另一个坑是相对地址以 // 开头(协议相对),urljoin 能处理,但如果你自己拼字符串就会漏掉协议。
现在还在纠结的是:正文少于 50 字符就退回整页,这个阈值对短资讯(比如一句话快讯)会误判,把导航也带进去。你们抓短资讯时是单独给一个阈值,还是按来源站配置?
0 条评论
还没有评论,写下第一条。