Python爬虫入门:安全合规的百度搜索数据采集实践

第一次接触Python爬虫时,最让人困惑的不是技术实现,而是如何在法律允许的范围内获取数据。记得三年前我刚学习爬虫时,花了两周时间研究各种反爬措施,却忽略了最重要的合规性问题,直到收到某平台的警告邮件才恍然大悟。本文将从一个负责任的技术学习者角度,分享如何用requests和BeautifulSoup构建一个符合伦理规范的百度搜索采集工具。

百度搜索作为爬虫学习对象有几个独特优势:首先,它的HTML结构相对稳定,对初学者友好;其次,作为国内主流搜索引擎,其公开结果页面在法律允许范围内可供个人学习使用;最重要的是,适度的反爬机制能让我们学习到基本的规避技巧,又不会因过于复杂而打击学习积极性。

1. 环境准备与基础认知

1.1 必要的Python库安装

开始前需要确保已安装以下两个核心库:

pip install requests beautifulsoup4
  • requests:用于发送HTTP请求和获取网页内容
  • beautifulsoup4:用于解析HTML文档并提取所需数据

提示:建议使用Python 3.6+版本,这是大多数现代库支持的最低版本

1.2 爬虫伦理与法律边界

在编写第一行代码前,必须明确几个基本原则:

  1. robots.txt协议:百度允许爬取搜索结果页,但明确禁止商业性大规模采集
  2. 访问频率控制:单IP请求间隔不应低于2秒,日请求量控制在1000次以内
  3. 数据使用限制:采集结果仅用于个人学习,禁止转售或用于商业分析
  4. User-Agent规范:必须声明真实身份,不可伪装成搜索引擎蜘蛛

下表对比了合规与违规操作的关键区别:

行为特征合规操作违规风险
请求频率2-5秒/次高频并发请求
数据规模少量关键词测试全量数据抓取
身份标识真实浏览器UA伪装搜索引擎bot
数据用途技术学习研究商业盈利行为

2. 核心代码实现与反爬策略

2.1 基础请求框架构建

我们先创建一个基本的搜索函数骨架:

import requests
from bs4 import BeautifulSoup
import random
import time

def baidu_search(keyword):
    """百度搜索采集函数"""
    # 构建搜索URL
    url = f"https://www.baidu.com/s?wd={keyword}"
    
    # 请求头设置
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'
    }
    
    # 发送请求
    response = requests.get(url, headers=headers)
    
    # 解析响应
    soup = BeautifulSoup(response.text, 'html.parser')
    
    # 数据提取逻辑...

这个基础版本存在明显问题:固定User-Agent和缺乏延时控制,极易触发反爬机制。

2.2 反爬措施增强方案

2.2.1 User-Agent轮换策略

维护一个常见浏览器UA列表,每次请求随机选择:

user_agents = [
    'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
    'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.1 Safari/605.1.15',
    'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:89.0) Gecko/20100101 Firefox/89.0',
    'Mozilla/5.0 (iPhone; CPU iPhone OS 14_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0 Mobile/15E148 Safari/604.1'
]

headers = {
    'User-Agent': random.choice(user_agents),
    'Accept-Language': 'zh-CN,zh;q=0.9',
    'Referer': 'https://www.baidu.com/'
}
2.2.2 请求间隔与异常处理

添加随机延时和重试机制:

def baidu_search(keyword, max_retry=3):
    for attempt in range(max_retry):
        try:
            time.sleep(random.uniform(1, 3))  # 1-3秒随机延时
            
            response = requests.get(url, headers=headers, timeout=10)
            response.raise_for_status()  # 检查HTTP状态码
            
            if "验证" in response.text:  # 简单验证码检测
                raise Exception("可能触发了验证码")
                
            return response.text
            
        except Exception as e:
            if attempt == max_retry - 1:
                raise
            time.sleep(5)  # 重试前延长等待

3. 数据解析与结果处理

3.1 HTML结构分析与定位

百度搜索结果页的主要结构如下:

<div class="result">
    <h3 class="t">
        <a href="真实链接">标题文本</a>
    </h3>
    <div class="c-abstract">摘要文本</div>
</div>

对应的解析代码:

results = []
for item in soup.find_all('div', class_='result'):
    try:
        title = item.find('h3').get_text(strip=True)
        link = item.find('a')['href']
        abstract = item.find('div', class_='c-abstract')
        abstract = abstract.get_text(strip=True) if abstract else ''
        
        results.append({
            'title': title,
            'link': link,
            'abstract': abstract
        })
    except AttributeError:
        continue

3.2 链接真实地址提取

百度搜索结果中的链接是跳转地址,需要提取真实URL:

import re

def get_real_url(redirect_url):
    """解析百度跳转链接"""
    try:
        resp = requests.head(redirect_url, allow_redirects=False, timeout=3)
        return resp.headers.get('Location', redirect_url)
    except:
        return redirect_url

4. 完整实现与优化建议

4.1 最终整合代码

import requests
from bs4 import BeautifulSoup
import random
import time
from urllib.parse import quote

class BaiduSearcher:
    def __init__(self):
        self.user_agents = [...]
        self.last_request_time = 0
        
    def make_request(self, url):
        """带控制的请求方法"""
        elapsed = time.time() - self.last_request_time
        if elapsed < 1:  # 确保至少1秒间隔
            time.sleep(1 - elapsed)
            
        headers = {
            'User-Agent': random.choice(self.user_agents),
            'Accept-Language': 'zh-CN,zh;q=0.9'
        }
        
        try:
            response = requests.get(url, headers=headers, timeout=8)
            self.last_request_time = time.time()
            return response
        except requests.exceptions.RequestException:
            return None
            
    def search(self, keyword, page=1):
        """执行搜索"""
        encoded_keyword = quote(keyword)
        url = f"https://www.baidu.com/s?wd={encoded_keyword}&pn={(page-1)*10}"
        
        response = self.make_request(url)
        if not response or response.status_code != 200:
            return []
            
        soup = BeautifulSoup(response.text, 'html.parser')
        return self.parse_results(soup)
        
    def parse_results(self, soup):
        """解析结果页"""
        items = []
        for result in soup.find_all('div', class_='result'):
            try:
                title_elem = result.find('h3')
                if not title_elem:
                    continue
                    
                title = title_elem.get_text(strip=True)
                link = self.get_real_url(title_elem.find('a')['href'])
                
                abstract_elem = result.find('div', class_='c-abstract')
                abstract = abstract_elem.get_text(strip=True) if abstract_elem else ''
                
                items.append({
                    'title': title,
                    'link': link,
                    'abstract': abstract
                })
            except Exception:
                continue
        return items
        
    def get_real_url(self, redirect_url):
        """获取跳转后的真实URL"""
        try:
            resp = requests.head(
                redirect_url,
                allow_redirects=False,
                timeout=3,
                headers={'User-Agent': random.choice(self.user_agents)}
            )
            return resp.headers.get('Location', redirect_url)
        except:
            return redirect_url

# 使用示例
searcher = BaiduSearcher()
results = searcher.search("Python爬虫入门", page=1)
for i, result in enumerate(results[:5], 1):
    print(f"{i}. {result['title']}\n   {result['link']}\n")

4.2 进一步优化方向

  1. IP代理池:当需要扩大采集规模时,应考虑使用优质代理服务
  2. 验证码识别:集成第三方验证码识别服务应对复杂情况
  3. 结果去重:使用Bloom Filter等算法高效去重
  4. 分布式架构:Scrapy-Redis框架适合大规模分布式采集

注意:所有优化必须建立在合规前提下,商业用途需获得平台官方授权

在实际项目中,我发现最容易被忽视的是Referer头的设置。合理模拟用户从百度首页发起搜索的行为(即设置Referer为baidu.com),能显著降低被屏蔽的概率。另一个实用技巧是在请求之间加入随机鼠标移动和滚动行为模拟,虽然我们的代码无法直接实现,但可以通过随机延时来近似这种人类操作节奏。

Logo

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐