Python 教程拆解索引 · 05:网络数据采集
说明:本文基于
Day61-65/做原创教学整理,不复写原教程内容。
本篇对应材料:
61.网络数据采集概述.md、62.用Python获取网络资源-1.md、62.用Python解析HTML页面-2.md、63.*并发编程*.md、64.使用Selenium抓取网页动态内容.md、65.爬虫框架Scrapy简介.md
定位更正:这一页现在只是“采集专题入口”。
requests、XPath、BS4、并发、Selenium、Scrapy 都应该拆开写,不能再压成一篇。
本章细教程
已经拆开的源文件
| 原仓库文件 | 主题 | 教程 |
|---|---|---|
Day61-65/62.用Python获取网络资源-1.md + 62.用Python解析HTML页面-2.md |
requests 获取内容、XPath、BS4/CSS 选择器解析 | 05A |
下一批应该继续拆的文件
Day61-65/63.Python中的并发编程-1.mdDay61-65/63.Python中的并发编程-2.mdDay61-65/63.Python中的并发编程-3.mdDay61-65/64.使用Selenium抓取网页动态内容.mdDay61-65/65.爬虫框架Scrapy简介.md
这部分最容易学废的方式,就是打开别人的爬虫代码一顿抄,结果连“抓取”和“解析”到底在干什么都没弄明白。
所以今天不碰真实网站,先做一个合法、安全、本地可复现的采集练习。
今天你要做出的结果
我们会完成三步:
- 解析一份本地 HTML;
- 批量抽取结构化数据;
- 用线程池并发处理多个页面字符串。
Step 1:先准备一份本地 HTML
新建 practice/day05/sample.html:
<!doctype html>
<html lang="zh-CN">
<body>
<div class="book" data-id="101">
<h2 class="title">Python 入门</h2>
<span class="price">59.00</span>
</div>
<div class="book" data-id="102">
<h2 class="title">FastAPI 实战</h2>
<span class="price">88.00</span>
</div>
</body>
</html>
Step 2:先做“解析”,不要急着上网络
新建 practice/day05/parse_local.py:
import re
from pathlib import Path
html = Path("sample.html").read_text(encoding="utf-8")
pattern = re.compile(
r'<div class="book" data-id="(\d+)">.*?'
r'<h2 class="title">(.*?)</h2>.*?'
r'<span class="price">(.*?)</span>',
re.S
)
books = []
for book_id, title, price in pattern.findall(html):
books.append({
"id": int(book_id),
"title": title,
"price": float(price),
})
print(books)
运行:
cd practice/day05
python parse_local.py
输出类似:
[{'id': 101, 'title': 'Python 入门', 'price': 59.0}, {'id': 102, 'title': 'FastAPI 实战', 'price': 88.0}]
这一步对应的就是 Day62:页面解析。
Step 3:再补一个“抓取”动作
如果你本机能联网,可以用一个公开演示站练请求;如果不想碰外网,这一步可以只看代码结构。
新建 practice/day05/fetch_demo.py:
import requests
url = "https://httpbin.org/html"
resp = requests.get(url, timeout=10)
print("状态码:", resp.status_code)
print("内容前80个字符:")
print(resp.text[:80])
运行:
python fetch_demo.py
这里你学到的是:抓取解决“把内容拿回来”,解析解决“把数据拆出来”。
Step 4:再做一个并发版
为了不碰真实多页面网站,我们用多个本地 HTML 字符串模拟任务。
新建 practice/day05/concurrent_parse.py:
from concurrent.futures import ThreadPoolExecutor
import re
pages = [
'<div class="book" data-id="1"><h2 class="title">A</h2><span class="price">10</span></div>',
'<div class="book" data-id="2"><h2 class="title">B</h2><span class="price">20</span></div>',
'<div class="book" data-id="3"><h2 class="title">C</h2><span class="price">30</span></div>',
]
pattern = re.compile(
r'data-id="(\d+)".*?<h2 class="title">(.*?)</h2>.*?<span class="price">(.*?)</span>',
re.S
)
def parse_one(html: str):
book_id, title, price = pattern.search(html).groups()
return {"id": int(book_id), "title": title, "price": float(price)}
with ThreadPoolExecutor(max_workers=3) as pool:
results = list(pool.map(parse_one, pages))
print(results)
这一步对应 Day63:并发在爬虫里不是炫技,而是在解决 I/O 等待。
什么时候才该去学 Selenium 和 Scrapy
你现在先不要急着上来就装大框架。
Selenium 该在什么时候出场
当页面内容依赖浏览器执行 JavaScript 后才出现时。
Scrapy 该在什么时候出场
当你的脚本开始出现这些问题时:
- 去重麻烦;
- 调度混乱;
- 重试逻辑难写;
- 数据管道越来越乱。
本篇练习
- 在
sample.html里再加两本书。 - 给解析结果加一个
price_level字段,比如大于 80 算"high"。 - 把并发版输出结果改成按价格从高到低排序。
常见坑
- 坑 1:上来就拿真实网站乱试。 先学会本地流程,再谈外网。
- 坑 2:把抓取和解析混成一坨。 这会让调试非常痛苦。
- 坑 3:一听并发就上异步全家桶。 先弄清楚为什么慢,再决定并发策略。
上一篇:Python 教程拆解 · 04:Django 与 FastAPI
下一篇:Python 教程拆解 · 06:数据分析与机器学习