(声明!本教程旨在进行技术了解与学习,不可用于恶意获取隐私信息、破坏互联网绿色环境的违法行为)
在这一篇中,我们将不再满足于抓取一个页面,而是要构建一个“两级爬虫”:一级负责“侦察”文章清单,二级负责“深度爬取”内容,并最终将它们整齐地存入 CSV 表格。
urljoin网页源码里的链接通常是相对路径(例如 /archives/hello),直接访问会报错,因此我们必须将其与基础路径(如https://blog.agenblog.cn/)拼接。那我们为什么要用 urljoin 而不是直接字符串相加?因为 urljoin 会智能处理末尾的斜杠和冗余路径,确保生成的 URL 永远是合法的。
.txt 文件适合存感言,而 .csv(逗号分隔值文件)适合存数据。它可以被 Excel 直接打开,是数据分析最通用的格式。
爬虫不是黑客攻击。连续高频的请求会给服务器造成巨大压力。引入 time.sleep(),每隔一定时间抓取一次,是爬虫开发者的一种职业操守。
下面这段参考代码这段代码整合了链接探测、纵深解析、编码修复、换行处理、以及 CSV 持久化存储。
import requests
from bs4 import BeautifulSoup
import csv
import time
from urllib.parse import urljoin
def get_article_links(base_url):
"""一级爬虫:扫描首页,获取所有文章链接清单"""
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
}
try:
response = requests.get(base_url, headers=headers, timeout=10)
response.encoding = response.apparent_encoding
soup = BeautifulSoup(response.text, 'html.parser')
links_pool = []
# 探测所有 a 标签
all_a_tags = soup.find_all('a', href=True)
for a in all_a_tags:
href = a['href']
# 过滤逻辑:只抓取包含 /archives/ 的链接,且排除重复
if '/archives/' in href:
full_url = urljoin(base_url, href)
if full_url not in links_pool:
links_pool.append(full_url)
return links_pool
except Exception as e:
print(f"首页扫描失败: {e}")
return []
def parse_detail_page(url):
"""二级爬虫:深入文章详情页,提取具体内容"""
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'}
try:
res = requests.get(url, headers=headers, timeout=10)
res.encoding = res.apparent_encoding
soup = BeautifulSoup(res.text, 'html.parser')
# 提取标题
title = soup.title.string.replace("- AgenBlog", "").strip() if soup.title else "未知标题"
# 提取正文并保留换行 (Halo 2.x 结构)
content_area = soup.find('section', class_='prose')
content = content_area.get_text(separator="\n", strip=True) if content_area else "正文内容为空"
return title, content
except Exception as e:
return None, None
# --- 主程序逻辑 ---
if __name__ == "__main__":
target_site = 'https://blog.agenblog.cn/'
print("第一步:正在侦察首页文章链接...")
articles = get_article_links(target_site)
print(f"侦察完毕!共发现 {len(articles)} 篇目标文章。\n")
# 准备 CSV 存储
with open('blog_data.csv', 'w', newline='', encoding='utf-8-sig') as f:
writer = csv.writer(f)
writer.writerow(['文章标题', '文章链接', '正文内容预览']) # 写入表头
# 循环爬取
for link in articles:
print(f"正在爬取:{link}")
title, content = parse_detail_page(link)
if title:
# 只存入正文前100个字符作为预览
writer.writerow([title, link, content[:100].replace('\n', ' ') + "..."])
print(f"已存入:{title}")
# 频率限制,每篇休息 1 秒
time.sleep(1)
print("\n 大功告成!所有数据已保存在 blog_data.csv 中。")

为什么代码里要写两个函数?
在编写中大型爬虫时,我们要遵循“单一职责原则”。一个函数负责“找路”(获取链接),一个函数负责“挖矿”(解析内容)。这样即使网站的首页布局改了,我们只需要修改第一个函数,而不需要动核心的解析逻辑。
处理 CSV 的“中文陷阱”
在写入 CSV 时,你可能注意到我使用了 encoding='utf-8-sig'。这是一个非常细微但关键的技巧。如果没有这个 -sig,直接用 Excel 打开生成的 CSV 可能会出现乱码;加上它,就是在文件头添加了一个“签名”,告诉 Excel:“嘿,我是中文,请正确显示我”。
去重逻辑的重要性
首页可能会有多个链接指向同一篇文章(比如点击图片或点击标题)。如果我们不加判断,爬虫就会对同一篇文章发起多次重复请求,既浪费带宽,又容易触发反爬。
通过这一篇,你已经完成了从“爬取一个网页”到“爬取一个站点”的跨越。你学会了如何构建两级工作流,并掌握了基本的数据的结构化存储。但你是否感觉,soup.find 在处理复杂的嵌套结构时依然显得有些笨重?
因此,在教程(三)中,我们将引入爬虫界的“核武器”——XPath 语法。它能让你像上帝视角一样,用一行代码精准定位网页中任何位置的数据,从而更高效的获取你想要的特定信息。