很多人刚学 Python 数据采集时,会把注意力全放在“怎么把网页抓下来”。
我刚开始也这样。代码跑通,终端里打印出一大片 HTML,就以为事情已经完成了一半。后来真正做项目才发现,网页采集只占很小一部分。数据能不能去重、能不能统一格式、最后能不能回答一个具体问题,才决定这份数据有没有用。
所以这篇不讲复杂反爬,也不碰登录、验证码和高频请求。我用一个公开的练习网站,带你从头跑通三个步骤:
用 Python 采集网页数据;
用 Pandas 清洗脏数据;
统计作者和标签,生成分析结果。
如果你是零基础,先把这条完整流程跑通,比一开始研究代理、并发和浏览器自动化更合适。
准备 Python 环境
先确认电脑安装了 Python 3.10 或更高版本。
在终端执行:
python --version
然后安装本次要用的库:
pip install requests beautifulsoup4 pandas matplotlib
四个库分工很清楚:
requests:发送网页请求;
BeautifulSoup:从 HTML 中提取内容;
pandas:清洗和统计数据;
matplotlib:生成简单图表。
建议新建一个文件夹,再创建 main.py 文件。后面的代码全部放进这个文件即可。
先采集网页数据
本次使用 quotes.toscrape.com。这是专门供采集练习使用的网站,页面中包含名言、作者和标签,还有分页结构。
先看采集部分:
from pathlib import Path
import time
import requests
import pandas as pd
from bs4 import BeautifulSoup
BASE_URL = "https://quotes.toscrape.com/page/{}/"
HEADERS = {
"User-Agent": (
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 Chrome/124.0 Safari/537.36"
)
}
def collect_quotes(max_pages=5):
records = []
session = requests.Session()
for page in range(1, max_pages + 1):
url = BASE_URL.format(page)
print(f"正在采集:{url}")
response = session.get(
url,
headers=HEADERS,
timeout=10
)
response.raise_for_status()
soup = BeautifulSoup(response.text, "html.parser")
quote_blocks = soup.select(".quote")
if not quote_blocks:
break
for block in quote_blocks:
quote = block.select_one(".text").get_text(
strip=True
).strip("“”")
author = block.select_one(".author").get_text(
strip=True
)
tags = [
tag.get_text(strip=True)
for tag in block.select(".tag")
]
records.append({
"quote": quote,
"author": author,
"tags": "|".join(tags),
"source_url": url
})
if not soup.select_one("li.next a"):
break
time.sleep(1)
return pd.DataFrame(records)
这段代码做了几件事。
首先,requests.Session() 会复用连接,比每次单独请求稍稳一些。其次,每个请求都设置了十秒超时,避免网络异常时程序一直卡住。最后,每采完一页暂停一秒,不连续轰炸网站。
我通常会保留 source_url 字段。以后发现某条数据不对,可以直接回到原页面核查。很多新手为了让表格看起来干净,会过早删除来源字段,后面排错反而更麻烦。
清洗采集结果
采集下来的数据不要直接拿去分析。先保留一份原始文件,再生成清洗后的版本。
def clean_quotes(df):
cleaned = df.copy()
cleaned["quote"] = (
cleaned["quote"]
.str.replace(r"\s+", " ", regex=True)
.str.strip()
)
cleaned["author"] = (
cleaned["author"]
.str.replace(r"\s+", " ", regex=True)
.str.strip()
)
cleaned["tags"] = cleaned["tags"].apply(
lambda value: "|".join(
sorted({
tag.strip().lower()
for tag in value.split("|")
if tag.strip()
})
)
)
cleaned = cleaned.dropna(
subset=["quote", "author"]
)
cleaned = cleaned[
(cleaned["quote"] != "")
& (cleaned["author"] != "")
]
cleaned = cleaned.drop_duplicates(
subset=["quote", "author"]
)
cleaned["quote_length"] = (
cleaned["quote"].str.len()
)
return cleaned.reset_index(drop=True)
这里主要处理四类问题:
删除多余空格;
过滤空内容;
统一标签格式;
按“名言加作者”去重。
去重时不要只按作者判断。一个作者可能有多条名言,只保留一条会误删正常数据。相反,只按名言去重也可能遇到同一句话被不同页面重复收录。实际项目中,去重字段要根据业务含义确定。
quote_length 是一个派生字段。它不是网页直接提供的,而是根据名言文本计算出来的,后面可以用来观察文本长度。
做第一轮数据分析
零基础的数据分析不用一上来就做复杂模型。先回答三个简单问题:
一共采集了多少条记录?
哪些作者出现得最多?
哪些标签最常见?
def analyze_quotes(df):
print(f"\n清洗后记录数:{len(df)}")
print(f"作者数量:{df['author'].nunique()}")
print(
f"平均文本长度:"
f"{df['quote_length'].mean():.1f}"
)
author_top = (
df["author"]
.value_counts()
.head(10)
)
tag_data = (
df.assign(tag=df["tags"].str.split("|"))
.explode("tag")
)
tag_top = (
tag_data.loc[tag_data["tag"] != "", "tag"]
.value_counts()
.head(10)
)
print("\n出现次数最多的作者:")
print(author_top)
print("\n出现次数最多的标签:")
print(tag_top)
return author_top, tag_top
这里的 value_counts() 用于统计每个值出现的次数。
标签稍微特殊一点。一条名言可能同时带有多个标签,所以需要先用 split() 拆分,再通过 explode() 把一行展开成多行,最后才能准确计数。
这也是数据清洗中很常见的情况:原始字段适合展示,不一定适合统计。分析之前,往往要先调整数据结构。
保存数据和分析图
最后补上文件保存与可视化代码:
def save_chart(author_top):
Path("output").mkdir(exist_ok=True)
chart_data = author_top.sort_values()
ax = chart_data.plot(
kind="barh",
figsize=(9, 6),
color="#4C78A8"
)
ax.set_title("Top Authors by Quote Count")
ax.set_xlabel("Quote Count")
ax.set_ylabel("Author")
figure = ax.get_figure()
figure.tight_layout()
figure.savefig(
"output/top_authors.png",
dpi=150
)
def main():
Path("data").mkdir(exist_ok=True)
raw_df = collect_quotes(max_pages=5)
if raw_df.empty:
raise RuntimeError("未采集到数据,请检查网络或页面结构。")
raw_df.to_csv(
"data/raw_quotes.csv",
index=False,
encoding="utf-8-sig"
)
clean_df = clean_quotes(raw_df)
clean_df.to_csv(
"data/clean_quotes.csv",
index=False,
encoding="utf-8-sig"
)
author_top, tag_top = analyze_quotes(clean_df)
author_top.rename("count").to_csv(
"data/author_top.csv",
encoding="utf-8-sig"
)
tag_top.rename("count").to_csv(
"data/tag_top.csv",
encoding="utf-8-sig"
)
save_chart(author_top)
print("\n处理完成,结果已保存到 data 和 output 目录。")
if __name__ == "__main__":
main()
把前面的代码按顺序放在同一个 main.py 文件中,然后执行:
python main.py
正常情况下,你会得到这些文件:
data/
├── raw_quotes.csv
├── clean_quotes.csv
├── author_top.csv
└── tag_top.csv
output/
└── top_authors.png
其中,raw_quotes.csv 是未经修改的原始数据,clean_quotes.csv 是清洗后的数据。两份都保留,方便后续核对。
运行失败时先检查什么
第一次运行,常见问题基本集中在下面几类。
如果提示 ModuleNotFoundError,说明依赖库没有安装成功。根据报错中的库名重新执行 pip install。
如果提示连接超时,可以先在浏览器里打开目标网站,确认当前网络能够访问,再适当调大 timeout。不要一看到超时就直接加并发,那通常只会让问题更难判断。
如果程序正常运行但结果为空,通常是网页结构发生变化。可以打印一小段 HTML 检查:
print(response.text[:500])
如果页面内容正常,就重新核对 .quote、.text 和 .author 这些 CSS 选择器。
零基础下一步学什么
跑完这个项目后,我建议按下面的顺序继续练习:
给采集函数增加失败重试和日志记录;
学习 CSV、Excel、JSON 三种常见数据格式;
使用 Pandas 处理缺失值、异常值和日期字段;
学习分组统计、透视表和多条件筛选;
最后再接触浏览器自动化、代理调度和并发采集。
顺序不要反过来。
我见过不少刚入门的人,第一周就开始研究代理池和多线程,结果连重复数据都没处理,采集十万条后才发现真正能用的不到一半。对零基础来说,先保证数据采得准、洗得干净、能够回答问题,比单纯追求采集数量重要得多。
还有一条边界需要提前说清楚:练习时优先使用公开测试站点、自有页面或已经获得授权的数据源。正式采集前要查看网站条款、访问限制和数据使用要求,不绕过登录、验证码或明确的访问控制。
把这套流程跑通,你就已经完成了一个最小但完整的数据项目:从网页获得原始数据,整理成结构化表格,再用统计结果回答具体问题。后面换成商品、资讯或公开目录,底层思路不会有太大变化。
