如何将Adblock规则应用到MechanicalSoup获取的网页?
嘿,我来帮你搞定这两个问题——先是那个烦人的Unicode编码声明报错,再是把Adblock规则顺利应用到你抓取的网页上。
一、先搞定编码声明的ValueError
你碰到的ValueError: Unicode strings with encoding declaration are not supported...这个问题,本质是lxml的etree解析器不接受带XML编码声明(比如<?xml version="1.0" encoding="UTF-8"?>)的Unicode字符串,它更偏好直接处理字节流或者不带声明的内容片段。这里给你两个简单的修复方案:
方案1:直接用字节流解析(推荐)
MechanicalSoup返回的page.content是原始字节数据,直接用它初始化etree就能绕开编码声明的坑:
import mechanicalsoup from lxml import etree browser = mechanicalsoup.StatefulBrowser() page = browser.open("https://your-target-url.com") # 直接用字节内容解析etree,完美避开编码问题 tree = etree.HTML(page.content)
方案2:手动移除编码声明再解析
如果你已经拿到了字符串格式的网页内容,手动砍掉开头的XML声明就行:
html_str = page.text # 检查并移除开头的XML编码声明 if html_str.startswith('<?xml'): html_str = html_str.split('?>', 1)[1].strip() tree = etree.HTML(html_str)
二、给抓取的网页应用Adblock规则
要把Adblock规则用到已下载的网页上,核心是解析规则,然后移除网页中匹配的元素。我们主要处理元素隐藏规则(比如##.ad-container、#ad-banner这类),因为网络请求类规则可以在抓取阶段拦截,这里先聚焦页面元素处理:
1. 解析Adblock规则
推荐用adblock-parser库来解析规则(先装一下:pip install adblock-parser),它能帮你处理大部分标准的Adblock语法:
from adblockparser import AdblockRules # 你的Adblock规则列表,替换成你自己的 adblock_rules = [ "##.ad-box", "#top-ad", "###sidebar-ad", "div[class*='advert']" ] # 初始化规则解析器 rules = AdblockRules(adblock_rules)
2. 移除匹配规则的元素
不管你用BeautifulSoup还是lxml etree,都可以遍历元素并检查匹配:
用BeautifulSoup实现(MechanicalSoup自带的soup对象直接用)
soup = page.soup # 直接拿MechanicalSoup返回的BeautifulSoup对象 # 遍历所有元素,检查是否要隐藏 for element in soup.find_all(): # 构造规则匹配需要的元素信息 selector_info = { "tag": element.name, "id": element.get("id", ""), "class": element.get("class", []), "attributes": element.attrs } # 匹配到就移除元素 if rules.should_hide_element(selector_info): element.decompose()
用lxml etree实现
# 写个递归函数遍历节点并移除广告元素 def remove_ad_elements(node): # 先处理子节点,避免遍历过程中节点被删除导致的问题 for child in list(node): remove_ad_elements(child) # 构造当前节点的匹配信息 tag = node.tag if hasattr(node, 'tag') else '' node_id = node.get('id', '') node_classes = node.get('class', []) attrs = node.attrib selector_info = { "tag": tag, "id": node_id, "class": node_classes, "attributes": attrs } # 匹配规则就移除 if rules.should_hide_element(selector_info): parent = node.getparent() if parent is not None: parent.remove(node) # 处理整个etree树 remove_ad_elements(tree)
3. 保存处理后的内容
处理完之后,你可以直接保留BeautifulSoup对象或者etree对象用于后续操作,也可以转成HTML文件保存:
# 保存BeautifulSoup处理后的内容 with open("cleaned_page.html", "w", encoding="utf-8") as f: f.write(soup.prettify()) # 保存etree处理后的内容 cleaned_html = etree.tostring(tree, encoding='utf-8', pretty_print=True).decode('utf-8') with open("cleaned_page_etree.html", "w", encoding="utf-8") as f: f.write(cleaned_html)
三、额外小贴士
- 有些复杂的Adblock规则(比如伪类、高级通配符)
adblock-parser可能支持得不够完美,如果遇到这种情况,可以手动扩展匹配逻辑。 - 如果要处理网络请求类的Adblock规则(比如阻止加载广告图片、脚本),可以给MechanicalSoup的session添加钩子,拦截匹配规则的请求。
内容的提问来源于stack exchange,提问作者Nikita Popov

