AgenBlog

爬虫教程(二)-自动化链接提取与数据结构化

2026/02/08
24
0

声明!本教程旨在进行技术了解与学习,不可用于恶意获取隐私信息、破坏互联网绿色环境的违法行为

在这一篇中,我们将不再满足于抓取一个页面,而是要构建一个“两级爬虫”:一级负责“侦察”文章清单,二级负责“深度爬取”内容,并最终将它们整齐地存入 CSV 表格。

核心知识点拆解

1. URL 的“缝合艺术”:urljoin

网页源码里的链接通常是相对路径(例如 /archives/hello),直接访问会报错,因此我们必须将其与基础路径(如https://blog.agenblog.cn/)拼接。那我们为什么要用 urljoin 而不是直接字符串相加?因为 urljoin 会智能处理末尾的斜杠和冗余路径,确保生成的 URL 永远是合法的。

2. 结构化存储:CSV 格式

.txt 文件适合存感言,而 .csv(逗号分隔值文件)适合存数据。它可以被 Excel 直接打开,是数据分析最通用的格式。

3. 礼貌爬取:频率限制

爬虫不是黑客攻击。连续高频的请求会给服务器造成巨大压力。引入 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 中。")

常见问题讲解

  1. 为什么代码里要写两个函数?

在编写中大型爬虫时,我们要遵循“单一职责原则”。一个函数负责“找路”(获取链接),一个函数负责“挖矿”(解析内容)。这样即使网站的首页布局改了,我们只需要修改第一个函数,而不需要动核心的解析逻辑。

  1. 处理 CSV 的“中文陷阱”

在写入 CSV 时,你可能注意到我使用了 encoding='utf-8-sig'。这是一个非常细微但关键的技巧。如果没有这个 -sig,直接用 Excel 打开生成的 CSV 可能会出现乱码;加上它,就是在文件头添加了一个“签名”,告诉 Excel:“嘿,我是中文,请正确显示我”。

  1. 去重逻辑的重要性

首页可能会有多个链接指向同一篇文章(比如点击图片或点击标题)。如果我们不加判断,爬虫就会对同一篇文章发起多次重复请求,既浪费带宽,又容易触发反爬。

总结

通过这一篇,你已经完成了从“爬取一个网页”到“爬取一个站点”的跨越。你学会了如何构建两级工作流,并掌握了基本的数据的结构化存储。但你是否感觉,soup.find 在处理复杂的嵌套结构时依然显得有些笨重?

因此,在教程(三)中,我们将引入爬虫界的“核武器”——XPath 语法。它能让你像上帝视角一样,用一行代码精准定位网页中任何位置的数据,从而更高效的获取你想要的特定信息。