You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何我的BeautifulSoup脚本无法解析下议院财务利益登记页HTM数据且报403错误?

解决403错误与爬虫代码优化方案

一、403错误的解决思路

英国议会网站的反爬机制会验证请求合法性,你的请求被拦截主要是头部模拟不够彻底,或会话状态未正确维护。可从以下几点调整:

  • 更新User-Agent:使用更近期的浏览器标识,避免过时版本信息。
  • 补充关键请求头:添加DNT、Upgrade-Insecure-Requests等常见头部,让请求更接近真实浏览器行为。
  • 维持会话一致性:确保所有请求通过同一个Session发送,保留Cookie状态。
  • 随机化请求间隔:将固定1秒休眠改为1-3秒随机范围,降低被识别为爬虫的概率。

二、代码优化建议

  1. 精准过滤MP链接:原代码通过cmregmem过滤会包含无关链接,应只提取格式为/pa/cm/cmregmem/240930/[MPID].htm的详情页链接。
  2. 结构化提取内容:原代码通过文本索引截取内容易出错,应利用BeautifulSoup定位标题标签后,提取后续段落或列表内容。
  3. 模块化请求逻辑:将页面请求与解析封装成函数,提升代码复用性和可读性。
  4. 增强异常处理:针对超时、连接错误、解析错误等分别处理,便于排查问题。
  5. 优化数据存储:将提取的数据保存为字典或CSV格式,方便后续分析,而非仅打印输出。

三、修改后的完整代码

import requests
from bs4 import BeautifulSoup
import time
import random
from typing import List, Dict

# 基础配置
BASE_URL = "https://publications.parliament.uk"
CONTENTS_URL = f"{BASE_URL}/pa/cm/cmregmem/240930/contents.htm"

# 模拟真实Chrome浏览器请求头(更新为较新版本)
HEADERS = {
    'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36',
    'Accept-Language': 'en-US,en;q=0.9',
    'Accept-Encoding': 'gzip, deflate, br',
    'Connection': 'keep-alive',
    'Referer': CONTENTS_URL,
    'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
    'DNT': '1',
    'Upgrade-Insecure-Requests': '1'
}

def fetch_page(session: requests.Session, url: str) -> BeautifulSoup:
    """封装页面请求与解析逻辑"""
    try:
        response = session.get(url, headers=HEADERS, timeout=15)
        response.raise_for_status()  # 主动抛出HTTP错误
        response.encoding = response.apparent_encoding  # 自动识别编码
        return BeautifulSoup(response.text, 'lxml')  # 用lxml解析器,速度更快
    except requests.exceptions.RequestException as e:
        print(f"请求失败 {url}: {str(e)}")
        return None

def extract_mp_shareholdings(mp_soup: BeautifulSoup) -> List[str]:
    """从MP页面提取持股信息"""
    if not mp_soup:
        return ["页面解析失败"]
    
    # 定位"Shareholdings"标题标签(通常是h2或h3)
    share_title = mp_soup.find(['h2', 'h3'], string=lambda text: text and "Shareholdings" in text)
    if not share_title:
        return ["未找到持股信息"]
    
    # 提取标题后直到下一个标题的内容
    share_content = []
    next_elem = share_title.next_sibling
    while next_elem and next_elem.name not in ['h2', 'h3']:
        if next_elem.name in ['p', 'li']:
            text = next_elem.get_text(strip=True)
            if text:
                share_content.append(text)
        next_elem = next_elem.next_sibling
    
    return share_content if share_content else ["未找到持股信息"]

def main():
    session = requests.Session()
    contents_soup = fetch_page(session, CONTENTS_URL)
    if not contents_soup:
        return
    
    mp_data: List[Dict[str, str]] = []
    # 精准过滤MP详情页链接
    mp_links = contents_soup.find_all('a', href=lambda href: href and href.startswith('/pa/cm/cmregmem/240930/') and href.endswith('.htm'))
    
    for link in mp_links:
        mp_name = link.get_text(strip=True)
        if not mp_name:
            continue
        
        mp_url = f"{BASE_URL}{link['href']}"
        mp_soup = fetch_page(session, mp_url)
        shareholdings = extract_mp_shareholdings(mp_soup)
        
        mp_data.append({
            'mp_name': mp_name,
            'shareholdings': shareholdings
        })
        
        # 随机休眠1-3秒
        time.sleep(random.uniform(1, 3))
        print(f"已处理:{mp_name}")
    
    # 打印结果(可改为保存为CSV/JSON)
    print("\n=== MP持股信息提取结果 ===")
    for item in mp_data:
        print(f"\n{item['mp_name']}:")
        for line in item['shareholdings']:
            print(f"  - {line}")

if __name__ == "__main__":
    main()

四、额外注意事项

  • 依赖安装:需安装lxml解析器(pip install lxml),比默认的html.parser更高效健壮。
  • 合规性:英国议会公开数据允许非商业用途爬取,需遵守网站robots.txt规则。
  • 增量爬取:若需定期追踪数据变化,可记录已爬取的MP页面URL,避免重复请求。

内容的提问来源于stack exchange,提问作者fariz

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 10:29:54