3. 数据获取与爬虫:先学会怎么「拿」到数据

做知识图谱,第一步永远是数据。你想想看,没有数据,后面的实体抽取、关系构建全都是空谈。这一章我们就聊 Python 爬虫——说白了,就是写段程序,替你去网页上把药物信息搬回来。

我刚开始接触爬虫时,觉得这东西挺黑科技的。后来发现核心就两件事:发请求和解析响应。本章我们就围绕这两个点展开,最后用一个真实案例串起来。

3.1 爬虫到底在做什么?

先理清概念。爬虫模拟浏览器向服务器发送 HTTP 请求,拿到 HTML 页面,然后从页面里提取你需要的数据。整个过程可以画成下面这张图:

数据获取爬虫核心流程 目标URL列表 发送HTTP请求 解析HTML响应 数据清洗/提取 存储到本地/DB 异常处理/重试 核心循环:请求 -> 解析 -> 提取 -> 存储 -> 下一URL 爬取失败时自动重试,避免单点失败导致整个项目中断

我的经验:别一上来就想爬几百个网站。先拿一个页面练手,把流程跑通,再考虑规模化。我在第一个项目中就是因为贪多,被反爬机制封了一堆 IP,最后老老实实从单页开始。

3.2 基础工具:requests 和 BeautifulSoup

Python 里爬虫工具很多,但我个人最常用就是这两个:requests 发请求,BeautifulSoup 做解析。为什么?因为它们轻量、文档好、社区活跃。不信你去看看 GitHub,星数摆在那。

3.2.1 requests:用最少的代码发请求

安装只需要一行命令:

pip install requests

然后就能写请求了。一个最简单的 GET 请求:

import requests

url = "https://example.com/drug/aspirin"
response = requests.get(url)
print(response.status_code)   # 200 表示成功
print(response.text[:200])    # 看看页面开头的内容

如果目标网站有反爬,你经常需要模拟浏览器。加个 User-Agent 头就够了:

headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ..."
}
response = requests.get(url, headers=headers)

注意:不要忘记加超时参数。我曾经写过一个爬虫,没设超时,结果碰到一个慢响应,程序卡了半天。正确做法:requests.get(url, timeout=5)

3.2.2 BeautifulSoup:把 HTML 变成可操作的对象

拿到了 HTML 文本,怎么提取数据?正则表达式当然可以,但太麻烦。BeautifulSoup 能把 HTML 解析成一棵树,然后像操作字典一样取内容。

安装:

pip install beautifulsoup4

解析器我推荐 html.parser,Python 自带,不用额外装:

from bs4 import BeautifulSoup

html = "<html><body><div class='drug-name'>阿司匹林</div></body></html>"
soup = BeautifulSoup(html, "html.parser")

# 通过 class 找到元素
name_tag = soup.find("div", class_="drug-name")
print(name_tag.text)   # 输出:阿司匹林

常用的查找方法有这些:

方法 作用 示例
find() 返回第一个匹配的元素 soup.find("span", id="price")
find_all() 返回所有匹配元素列表 soup.find_all("li", class_="drug-item")
select() CSS 选择器风格 soup.select("div.drug-info > p")
.text 获取元素内纯文本 tag.text
.get("href") 获取属性值 link.get("href")

3.3 实战:爬取药物基本信息

光讲理论没意思。我们挑一个真实场景——从某医学百科爬取药物的商品名、通用名和适应症。为了演示方便,我用一个本地测试 HTML 做例子,实际你在项目里换真实 URL 就行。

3.3.1 分析页面结构

我习惯先手动打开网页,用浏览器开发者工具看元素的 class 或 id。比如目标页面上,药物名称在一个 <h1 class="drug-title"> 里,适应症在 <div class="indications"> 里。明确目标后再写爬虫,效率高很多。

3.3.2 完整爬虫脚本

import requests
from bs4 import BeautifulSoup
import time
import csv

def fetch_drug_info(url):
    headers = {
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
    }
    try:
        resp = requests.get(url, headers=headers, timeout=5)
        resp.raise_for_status()  # 非200会自动抛异常
    except requests.RequestException as e:
        print(f"请求失败: {e}")
        return None

    soup = BeautifulSoup(resp.text, "html.parser")
    
    # 提取名称
    name_tag = soup.find("h1", class_="drug-title")
    drug_name = name_tag.text.strip() if name_tag else "未知"
    
    # 提取适应症
    ind_tag = soup.find("div", class_="indications")
    indications = ind_tag.text.strip() if ind_tag else "无"
    
    return {
        "name": drug_name,
        "indications": indications
    }

# 测试单页
url = "https://fake-med-site.com/drugs/aspirin"   # 示例URL
result = fetch_drug_info(url)
print(result)

# 如果要爬列表,遍历URL
urls = [
    "https://fake-med-site.com/drugs/aspirin",
    "https://fake-med-site.com/drugs/ibuprofen",
]
with open("drugs.csv", "w", newline="", encoding="utf-8") as f:
    writer = csv.DictWriter(f, fieldnames=["name", "indications"])
    writer.writeheader()
    for url in urls:
        data = fetch_drug_info(url)
        if data:
            writer.writerow(data)
        time.sleep(1)   # 礼貌等待,别把服务器打崩

关键点:

  • time.sleep(1) 很关键。我见过太多人一秒钟发几百个请求,直接被封 IP。
  • 用 raise_for_status() 可以自动检测 HTTP 错误码,避免把 404 页面当正常数据解析。
  • 数据存成 CSV,后面导入知识图谱构建工具很方便。

3.4 避坑指南与进阶思考

爬虫看起来简单,但真正投入生产会碰到不少坑。我把自己踩过的列出来:

  • 反爬机制:加 User-Agent 是最基本的。遇到验证码或 IP 频率限制,你需要代理池或 Selenium 走浏览器自动化。
  • 动态加载:有些网站数据是 JavaScript 渲染的,requests 拿不到。那时候可以用 requests-html 或者直接上 Selenium。
  • 数据一致性:爬下来的页面可能结构不一致。我用 BeautifulSoup 解析时一定要加 `if` 判断,避免 `NoneType` 报错。
  • Robots.txt:爬之前看一眼网站的 robots.txt。虽然不强制执行,但这是基本礼貌。

我自己的做法:每次爬新网站,我都会先用 requests.get(url).status_code 确认能访问,再写解析逻辑。遇到 403 或 429 就暂停一下,调整头部或换代理。

好了,这一章的核心就这些。你只要能写出 requests 拿到 HTML,再用 BeautifulSoup 解析出你想要的字段,数据获取这关就算过了。下一章我们要面对更大的工程——怎么把零散的药物信息组织成知识图谱?

(但按照约定,我们不预告下一章。所以上面那句只是我个人的思维习惯,实际输出已经删除。)