《从BS4到lxml,XPath真没那么难——爬虫解析实战对比》
写爬虫快两个月了一直用的都是 BeautifulSoup简称 BS4。在排行榜页面、商品列表这类结构化清晰的页面上soup.find(h1, class_title)这种写法很方便加上 CSS 选择器的soup.select()也能应付大部分需求。直到某天遇到一个需求想从一个包含特定文本的div往上找它的父容器或者按序号提取列表里的第3个元素。BS4 虽然也能做但代码写出来总感觉别扭——循环、条件判断、.parent链式调用几行才能搞定的事情直觉告诉我应该有更简洁的方式。于是试了一下 lxml XPath。一、同一个页面两种写法对比先看一个最简单的场景从 CSDN 文章页面提取标题。BS4 写法from bs4 import BeautifulSoup soup BeautifulSoup(html, html.parser) title soup.find(h1, class_title-article).text.strip()lxml XPath 写法from lxml import etree html_tree etree.HTML(html) title html_tree.xpath(//h1[classtitle-article]/text())[0].strip()二、XPath 真正好用的三个场景场景一按文本内容定位比如抓取文章页面上的阅读量。页面上通常是这样div classarticle-bar-bottom span classread-count阅读 998 次/span /divBS4 是这么取的read_span soup.find(span, class_read-count)XPath 除了按 class 取还能直接按文本内容定位# 找到文本包含阅读的 span 元素 read_span html_tree.xpath(//span[contains(text(),阅读)])[0]表面上看只是写法不同但实际工作中经常会遇到某个元素的 class 是动态生成的比如classarticle-bar-xxxx后面的字符每次刷新都不一样这时候 class 选择器就失效了。而 XPath 的contains(text(),...)依然能精准命中。场景二按序号取元素爬取列表页时BS4 拿到的是列表再通过索引取值items soup.find_all(div, class_list-item) third_item items[2] # 取第3个XPath 直接在路径里指定序号# 取第3个 div.list-item third_item html_tree.xpath((//div[classlist-item])[3])[0]这个写法在提取表格行、商品列表、搜索结果等场景下非常直观一步到位。而且序号从 1 开始计数和人类的阅读习惯一致。场景三往上找父元素这是 XPath 最让我意外的好用之处。有时候我们定位到了一个子元素但需要的是它的父容器。BS4 的做法child soup.find(span, class_target) parent child.find_parent(div, class_card-container)如果中间隔了好几层父级需要链式调用.parent很多次或者用.find_parent()往上筛。XPath 的做法# 找到包含特定文本的 span然后往上找到父 div.card-container parent html_tree.xpath(//span[contains(text(),目标)]/ancestor::div[contains(class,card-container)])[0]一句话就搞定了。ancestor::会向上查找所有祖先配合条件筛选出符合条件的那个。同理preceding-sibling::可以找前面的兄弟元素following-sibling::可以找后面的兄弟元素。这在解析不规则页面结构时非常实用。三、什么时候该用哪个BS4 和 lxmlXPath 不是二选一的关系在我看来是使用场景的区别场景推荐用原因页面结构规整简单的 class/id 选择BS4代码短可读性好需要按文本内容定位lxml/XPathcontains(text())直接搞定需要按序号取元素lxml/XPath路径里带序号一步到位需要往上找父元素或兄弟元素lxml/XPathancestor::、preceding-sibling::日常临时调试BS4新手友好上手快现在我的爬虫脚本是两者混着用的80% 的解析用 BS4 写遇到上面那三种情况就换成 lxmlXPath。不冲突两样都会反而更灵活。四、一个最简单的调试方法写 XPath 最怕的是在代码里跑了一遍才发现写错了又得重新跑一遍很浪费时间。有个更快的办法直接在浏览器里试选择器对不对确认无误再贴到代码里。操作步骤随便打开一个网页比如你的 CSDN 文章页面按一下键盘上的F12键会弹出一个开发者工具的面板。在这个面板里找到Console这个标签页一般都在顶部点一下它出现一个可以输入文字的地方。在输入框里打上这段$x(//h1)然后按回车。浏览器会立刻列出当前页面里所有的h1标题元素。如果列表是空的说明你的 XPath 写错了当场就能改不用等到跑 Python 才发现。如果想直接看提取出来的文本内容可以输入$x(//h1/text())回车后会直接显示标题文字。为什么要学这个平时写爬虫的流程是写好代码 → 运行 → 报错 → 改代码 → 再运行改一次选择器就要跑一次 Python。而在浏览器 Console 里试几秒钟就能确认写法对不对确认没问题了再贴到代码里一次跑通。五、从零上手安装到跑通第一步装库pip install lxml如果下载慢加清华镜像pip install lxml -i https://pypi.tuna.tsinghua.edu.cn/simple第二步打开任意网页获取 HTML这里以 CSDN 任意一篇文章《Python爬虫实战抓取商品信息并导出Excel》链接为例你也可以换成自己常看的网站import requests from lxml import etree url https://blog.csdn.net/2603_96487003/article/details/162526677 resp requests.get(url, headers{ User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 }) html etree.HTML(resp.text)第三步提取标题title html.xpath(//h1[classtitle-article]/text())[0].strip() print(title)第四步提取阅读量和点赞数read html.xpath(//span[contains(class,read-count)]/text())[0].strip() like html.xpath(//span[contains(class,like-count)]/text())[0].strip() print(标题, title) print(阅读, read) print(点赞, like)运行看一下结果标题Python爬虫实战抓取商品信息并导出Excel 阅读1.1k 点赞25数据拿到了。全流程只有 4 步装库 → 请求页面 → 解析 HTML → 写 XPath 提取数据。等一下为什么阅读量显示的是1.1k而不是1100这是网站的习惯写法——当阅读量超过 1000 时CSDN 会自动把数字缩写加一个 k 表示千。1.1k就是11002.5k就是2500以此类推。如果想让数据变成纯数字方便后续做统计、画图表可以用一行代码转换def parse_count(text): text text.strip() if k in text.lower(): return int(float(text.lower().replace(k, )) * 1000) if w in text.lower(): # 超过1万会显示w return int(float(text.lower().replace(w, )) * 10000) return int(text) read_raw html.xpath(//span[contains(class,read-count)]/text())[0].strip() read_num parse_count(read_raw) # 1.1k → 1100很多网站微博、知乎、GitHub都有类似的缩写习惯做爬虫迟早都会遇到提前知道怎么处理就行。六、总结XPath 不是替代 BS4 的是补充 BS4 的。掌握它最核心的三板斧就够了按文本内容定位——contains(text(),关键字)class 变了也能找到按序号取元素——(//div)[3]一步到位不用先拿列表再索引按路径找父元素——ancestor::、preceding-sibling::从子元素往上反向定位实际项目中自然知道什么时候该掏哪把工具。不冲突两样都会反而更灵活。有爬虫数据采集需求可联系点个关注吧qaq~———————————————————————————————————————————版权声明本文为博主原创文章遵循 CC 4.0 BY-SA 版权协议转载请附上原文出处链接和本声明。