
很多人刚学 Python 数据采集时,会把注意力全放在“怎么把网页抓下来”。
我刚开始也这样。代码跑通,终端里打印出一大片 HTML,就以为事情已经完成了一半。后来真正做项目才发现,网页采集只占很小一部分。数据能不能去重、能不能统一格式、最后能不能回答一个具体问题,才决定这份数据有没有用。
所以这篇不讲复杂反爬,也不碰登录、验证码和高频请求。我用一个公开的练习网站,带你从头跑通三个步骤:
如果你是零基础,先把这条完整流程跑通,比一开始研究代理、并发和浏览器自动化更合适。
先确认电脑安装了 Python 3.10 或更高版本。
在终端执行:
python --version然后安装本次要用的库:
pip install requests beautifulsoup4 pandas matplotlib四个库分工很清楚:
requests:发送网页请求;BeautifulSoup:从 HTML 中提取内容;pandas:清洗和统计数据;matplotlib:生成简单图表。建议新建一个文件夹,再创建 main.py 文件。后面的代码全部放进这个文件即可。
本次使用 quotes.toscrape.com。这是专门供采集练习使用的网站,页面中包含名言、作者和标签,还有分页结构。
先看采集部分:
from pathlib import Pathimport timeimport requestsimport pandas as pdfrom bs4 import BeautifulSoupBASE_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.csvoutput/└── top_authors.png其中,raw_quotes.csv 是未经修改的原始数据,clean_quotes.csv 是清洗后的数据。两份都保留,方便后续核对。
第一次运行,常见问题基本集中在下面几类。
如果提示 ModuleNotFoundError,说明依赖库没有安装成功。根据报错中的库名重新执行 pip install。
如果提示连接超时,可以先在浏览器里打开目标网站,确认当前网络能够访问,再适当调大 timeout。不要一看到超时就直接加并发,那通常只会让问题更难判断。
如果程序正常运行但结果为空,通常是网页结构发生变化。可以打印一小段 HTML 检查:
print(response.text[:500])如果页面内容正常,就重新核对 .quote、.text 和 .author 这些 CSS 选择器。
跑完这个项目后,我建议按下面的顺序继续练习:
顺序不要反过来。
我见过不少刚入门的人,第一周就开始研究代理池和多线程,结果连重复数据都没处理,采集十万条后才发现真正能用的不到一半。对零基础来说,先保证数据采得准、洗得干净、能够回答问题,比单纯追求采集数量重要得多。
还有一条边界需要提前说清楚:练习时优先使用公开测试站点、自有页面或已经获得授权的数据源。正式采集前要查看网站条款、访问限制和数据使用要求,不绕过登录、验证码或明确的访问控制。
把这套流程跑通,你就已经完成了一个最小但完整的数据项目:从网页获得原始数据,整理成结构化表格,再用统计结果回答具体问题。后面换成商品、资讯或公开目录,底层思路不会有太大变化。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。