为何我的BeautifulSoup脚本无法解析下议院财务利益登记页HTM数据且报403错误?
解决403错误与爬虫代码优化方案
一、403错误的解决思路
英国议会网站的反爬机制会验证请求合法性,你的请求被拦截主要是头部模拟不够彻底,或会话状态未正确维护。可从以下几点调整:
- 更新User-Agent:使用更近期的浏览器标识,避免过时版本信息。
- 补充关键请求头:添加
DNT、Upgrade-Insecure-Requests等常见头部,让请求更接近真实浏览器行为。 - 维持会话一致性:确保所有请求通过同一个
Session发送,保留Cookie状态。 - 随机化请求间隔:将固定1秒休眠改为1-3秒随机范围,降低被识别为爬虫的概率。
二、代码优化建议
- 精准过滤MP链接:原代码通过
cmregmem过滤会包含无关链接,应只提取格式为/pa/cm/cmregmem/240930/[MPID].htm的详情页链接。 - 结构化提取内容:原代码通过文本索引截取内容易出错,应利用BeautifulSoup定位标题标签后,提取后续段落或列表内容。
- 模块化请求逻辑:将页面请求与解析封装成函数,提升代码复用性和可读性。
- 增强异常处理:针对超时、连接错误、解析错误等分别处理,便于排查问题。
- 优化数据存储:将提取的数据保存为字典或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
相关产品推荐
相关产品推荐

