第2章:文献数据源类型——PDF、HTML、XML、数据库
做文献抽取,第一关就是跟数据源打交道。
你拿到的材料是PDF还是HTML?是XML还是直接从数据库导出的?这直接决定了你的解析策略。说实话,我在这个坑里摔过好几次,今天跟你聊聊我的经验。
2.1 四大数据源概览
先看一张总览图,把四种数据源的逻辑关系搞明白。
四种数据源,各有各的脾气。我个人习惯先把它们分成两大类:结构化和非结构化。
| 数据源 | 结构化程度 | 主战场 | 核心工具 |
|---|---|---|---|
| 低(视觉排版) | 学术论文、报告、扫描件 | PyMuPDF、Poppler、OCR库 | |
| HTML | 中(半结构化) | 网页文章、期刊网站 | BeautifulSoup、LXML |
| XML | 高(Schema约束) | JATS、PubMed、TEI | XPath、XSLT |
| 数据库 | 最高(关系模型) | 文献管理系统、元数据仓库 | SQL、ORM |
2.2 PDF:最难缠的对手
PDF几乎是每个做文献抽取的人都会遇到的第一堵墙。为什么?因为PDF本质上是打印描述语言,不是文档描述语言。它只告诉渲染器"在坐标(100,200)处放字母'A'",至于这个'A'是标题还是正文,它不管。
我在项目里遇到过一本医学期刊的PDF,文字被分割得七零八落。一段正常的摘要,读出来是一串乱码。原因很简单——PDF生成工具把每个字符当独立对象处理了。
2.2.1 常见PDF类型
- 文本型PDF:文字可选中、可拷贝,直接提取即可
- 扫描型PDF:其实是图像,需要OCR技术
- 混合型PDF:部分文字可选,部分为图像(如公式、图表)
- 表单型PDF:有填充字段,少见但携带结构化数据
避坑指南:我曾经以为所有PDF都能用同一个库搞定。结果在一次批量抽取时,PyMuPDF读出来全是空白,换pdfplumber也不行。最后发现文件是扫描版,得先跑OCR。所以……拿到PDF先看类型,别盲目上工具。
2.2.2 抽取策略
文本型PDF,用PyMuPDF(fitz)或者pdfplumber。扫描型,得上Tesseract OCR或者云服务。我的经验是:能用文本提取就别OCR,OCR会引入拼写错误和版面混乱。
import fitz # PyMuPDF
doc = fitz.open("paper.pdf")
for page_num, page in enumerate(doc):
text = page.get_text()
print(f"--- Page {page_num+1} ---")
print(text)
小技巧:如果你发现提取的文本顺序混乱,试试设置sort=True参数。PyMuPDF默认按物理坐标排序,有时候能救你一命。
2.3 HTML:半结构化的双刃剑
HTML比PDF友好得多——至少它有标签。但别高兴太早。实际网页的HTML通常夹杂大量广告、导航栏、侧边栏、推荐链接。你的目标文本可能藏在第5层嵌套的<div>里。
说白了,HTML抽取的核心不是提取,是去除噪声。
2.3.1 工具选择
- BeautifulSoup:上手快,适合中小规模
- LXML:速度快,适合大规模批处理
- Readability算法:自动提取正文(Mozilla出品,适合新闻类页面)
from bs4 import BeautifulSoup
html = requests.get("https://example.com/article").text
soup = BeautifulSoup(html, "html.parser")
# 定位特定div,比如class="article-body"
content = soup.find("div", class_="article-body")
if content:
text = content.get_text(separator="\n", strip=True)
print(text)
避坑指南:不要直接用soup.get_text()提取全文。我踩过一次,抓下来500KB文本,实际有效内容只有20KB,其余全是页脚、版权声明和广告。先用选择器缩小范围。
2.3.2 动态HTML问题
现在很多期刊网站用JavaScript渲染内容,直接请求HTML拿不到数据。这需要Selenium或者Playwright来模拟浏览器。我不建议一上来就搞动态抓取——能静态搞定就别上浏览器,慢得太多了。
2.4 XML:结构化爱好者的天堂
如果你遇到XML,恭喜你,这是最规范的数据源。像JATS(Journal Article Tag Suite)这种标准格式,每个元素都有明确的语义标记。
比如JATS中的典型片段:
<article>
<front>
<article-meta>
<title-group>
<article-title>Deep Learning Applications in Medical Imaging</article-title>
</title-group>
</article-meta>
</front>
<body>
<sec>
<title>Introduction</title>
<p>Recent advances in deep learning ...</p>
</sec>
</body>
</article>
XML的抽取规则很简单——用XPath或XSLT。我建议优先用XPath,因为它直接、可读。
from lxml import etree
tree = etree.parse("article.xml")
titles = tree.xpath("//article-title/text()")
abstracts = tree.xpath("//abstract/p/text()")
for title, abstract in zip(titles, abstracts):
print(f"标题: {title}")
print(f"摘要: {abstract}")
print("---")
小技巧:如果XML有命名空间(namespace),XPath写法会变复杂。用xpath(namespaces)参数指定前缀映射,或者直接用local-name()绕过命名空间。
2.5 数据库:最省心的选择
数据库里的数据已经是结构化字段了。你要做的不是"抽取",而是"查询"。常见的场景包括:从文献管理系统的MySQL/PostgreSQL里导出元数据,或者从MongoDB里读取非标文档。
对于结构化数据库,核心问题反而不是抽取技术,而是数据质量和字段理解。比如同一个期刊名称在库里可能有三种写法:"Nature"、"Nature Journal"、"Nat."。
import sqlite3
conn = sqlite3.connect("papers.db")
cursor = conn.cursor()
cursor.execute("""
SELECT title, authors, year, doi
FROM papers
WHERE year >= 2019
ORDER BY year DESC
""")
for row in cursor.fetchall():
print(f"[{row[2]}] {row[0]} — {row[1]}")
避坑指南:我碰到过一个数据库,同一个字段里既有作者全名,又混了机构名和邮箱,用逗号分隔。这种"伪结构化"数据比纯文本还坑,抽出后一定要做清洗和校验。
2.6 数据源选择优先级
如果让你重新选数据源,我的建议优先级是这样的:
- 数据库 > XML > HTML > PDF
- 为什么?结构化程度越高,抽取的准确率越高,质控成本越低。
- 现实情况是:你手里往往只有PDF或HTML,那就只能硬着头皮上。