首页
/ Scrapy Selectors 完全指南:用 XPath、CSS 与 EXSLT 扩展高效提取网页数据

Scrapy Selectors 完全指南:用 XPath、CSS 与 EXSLT 扩展高效提取网页数据

2026-09-04 19:04:40作者:伍霜盼Ellen

网页抓取中最核心的任务,就是从 HTML 源码中把结构化数据挑出来。本篇以 Scrapy 官方文档 Selectors 章节 为主体,完整覆盖 Selector 的构造方式、get()/getall() 新 API、CSS 伪元素扩展、嵌套选择、正则提取、XPath 高阶技巧(变量、命名空间、EXSLT)等内容,并结合当前仓库的 Selector 源码Response 快捷方法实现单元测试 逐层印证其底层行为,帮助你从"会写一行 xpath"进阶到"能稳定、可维护地编写生产级抓取逻辑"。

为什么 Scrapy 自研 Selector:与 BeautifulSoup、lxml 的关系

文档首先给出了行业背景:Python 生态中常见的 HTML 数据提取库包括 BeautifulSoup(构建 Python 对象树、容错性好但慢)和 lxml(基于 ElementTree 风格的 XML/HTML 解析库,非标准库)。Scrapy 则自带一套提取机制——Selectors,它通过 XPath 或 CSS 表达式"选中" HTML 文档的特定部分。

文档中有一段关键说明:Scrapy Selectors 是对 parsel 库的一层薄封装,封装的目的是更好地与 Scrapy 的 Response 对象集成。parsel 是一个可脱离 Scrapy 独立使用的网页抓取库,底层使用 lxml,并在其上实现了更简洁的 API——因此 Scrapy selectors 的速度与解析精度与 lxml 基本一致。从 依赖声明 中可以看到 Scrapy 要求 parsel>=1.5.0;而 响应对象的 jmespath() 快捷方法 会在 parsel 版本不足时明确抛出提示,说明 JSON 选择器能力依赖较新版本的 parsel。

构造 Selector:response.selector 与手动构造

Response 对象暴露的 Selector

Response 对象在 .selector 属性上暴露一个 Selector 实例。由于"用 XPath/CSS 查询响应"极为常见,Response 还提供了两个快捷方法:response.xpath()response.css()

>>> response.xpath("//span/text()").get()
'good'
>>> response.css("span::text").get()
'good'

从源码看,TextResponse 用一个缓存属性懒加载 Selector,这正是"响应体只解析一次"承诺的实现基础(selector 属性):

@property
def selector(self) -> Selector:
    if self._cached_selector is None:
        # circular import
        from scrapy.selector import Selector
        self._cached_selector = Selector(self)
    return self._cached_selector

xpath()/css() 快捷方法只是转发给该缓存 Selector(快捷方法实现)。因此文档强调:通常无需手动构造 Selector——Spider 回调里总有 response 可用,使用 response.css()response.xpath()response.selector 还能确保响应体只被解析一次。

从文本或响应对象直接构造

需要脱离 Response 时使用 Selector 直接构造,支持两种互斥入参(text 字符串,或 response 对象;两者同时传会抛 ValueError,这一点在 构造器源码测试用例 test_selector_bad_args 中都有明确约束):

>>> from scrapy.selector import Selector
>>> body = "<html><body><span>good</span></body></html>"
>>> Selector(text=body).xpath("//span/text()").get()
'good'

>>> from scrapy.http import HtmlResponse
>>> response = HtmlResponse(url="http://example.com", body=body, encoding="utf-8")
>>> Selector(response=response).xpath("//span/text()").get()
'good'

Selector 会根据输入类型自动选择最佳解析规则(XML vs HTML)。源码中这段类型推断逻辑(unified.py)展示了完整的判定链:

  • XmlResponse → 类型 xml
  • JsonResponse → 类型 json
  • HtmlResponse 或未传 response(即 text= 构造)→ 类型 html
  • 其他响应类型保持 type 未设置,交由 parsel 从响应体内容推断。

测试文件 中的 test_flavor_detection 用同一段"坏标记" <div><img src="a.jpg"><p>Hello</div> 分别喂给 XmlResponse 与 HtmlResponse,验证了两种解析器行为差异(HTML 解析器自动闭合标签,XML 解析器不会)。文档还补充了一个实用技巧:当网站返回错误的 Content-Type 时,selector 类型会跟着错,此时可用 response.replace(cls=HtmlResponse) 把响应"重新铸造"为正确的响应类。

实战演练:Scrapy shell 与示例页面

文档选用了仓库内真实存在的示例页面 selectors-sample1.html 作为贯穿全文的练习素材。先用 shell 打开它(scrapy shell 的实现见 shell 命令):

scrapy shell https://docs.scrapy.org/en/latest/_static/selectors-sample1.html

shell 加载后,响应对象以 response 变量提供,其附带的 selector 即 response.selector。由于内容是 HTML,selector 会自动使用 HTML 解析器。

该页面的完整 HTML 如下(与文档 topics-selectors-htmlcode 锚点内容一致):

<!DOCTYPE html>

<html>
  <head>
    <base href='http://example.com/' />
    <title>Example website</title>
  </head>
  <body>
    <div id='images'>
      <a href='image1.html'>Name: My image 1 <br /><img src='image1_thumb.jpg' alt='image1'/></a>
      <a href='image2.html'>Name: My image 2 <br /><img src='image2_thumb.jpg' alt='image2'/></a>
      <a href='image3.html'>Name: My image 3 <br /><img src='image3_thumb.jpg' alt='image3'/></a>
      <a href='image4.html'>Name: My image 4 <br /><img src='image4_thumb.jpg' alt='image4'/></a>
      <a href='image5.html'>Name: My image 5 <br /><img src='image5_thumb.jpg' alt='image5'/></a>
    </div>
  </body>
</html>

get() 与 getall() 的语义区别

先选出 <title> 标签内的文本:

>>> response.xpath("//title/text()")
[<Selector query='//title/text()' data='Example website'>]

XPath 表达式本身只返回 Selector 列表,真正取出文本必须调用 .get().getall()

>>> response.xpath("//title/text()").getall()
['Example website']
>>> response.xpath("//title/text()").get()
'Example website'

语义规则要牢记:.get() 永远返回单个结果——多个匹配时取第一个,没有匹配时返回 None.getall() 返回包含全部结果的一个列表。取第一个匹配元素也可直接 .get()

>>> response.xpath('//div[@id="images"]/a/text()').get()
'Name: My image 1 '

# 未匹配时返回 None
>>> response.xpath('//div[@id="not-exists"]/text()').get() is None
True

# 可用 default 参数替代 None
>>> response.xpath('//div[@id="not-exists"]/text()').get(default="not-found")
'not-found'

CSS 扩展伪元素:::text 与 ::attr()

按 W3C 标准,CSS 选择器不支持选取文本节点或属性值。但这两类选取在网页抓取场景太刚需了,Scrapy(准确说是 parsel)因此实现了一组非标准伪元素

  • 选取文本节点:::text
  • 选取属性值:::attr(name)(name 为目标属性名)

文档此处有明确的 warning:这两个伪元素是 Scrapy/Parsel 私有的,大概率在 lxml、PyQuery 等其他库中无法使用。

示例(均基于上文示例页面):

# 选取 <title> 后代的子文本节点
>>> response.css("title::text").get()
'Example website'

# 选取当前上下文中所有后代文本节点
>>> response.css("#images *::text").getall()
['\n   ', 'Name: My image 1 ', '\n   ',
 'Name: My image 2 ', '\n   ', 'Name: My image 3 ',
 '\n   ', 'Name: My image 4 ', '\n   ', 'Name: My image 5 ', '\n  ']

# 元素存在但文本为空时返回空列表
>>> response.css("img::text").getall()
[]
>>> response.css("img::text").get()          # 无匹配 → None
>>> response.css("img::text").get(default="") # 需要字符串时用 default=''
''

# 选取链接的 href 属性值
>>> response.css("a::attr(href)").getall()
['image1.html', 'image2.html', 'image3.html', 'image4.html', 'image5.html']

一个易踩的坑值得单独强调:foo::text 在元素存在但没有文本(即文本为空)时不产生任何结果,所以 .css('foo::text').get() 可能返回 None;如果你总想拿到字符串,请显式使用 default=''

另外文档注明:这些伪元素不能链式使用——实践中也没意义,文本节点没有属性,属性值本身已是字符串、没有子节点。

嵌套 Selector:链式选择的核心模式

.xpath().css() 都返回 SelectorList——一组新 Selector 的列表(SelectorList 是内建 list 的子类,定义见 unified.py)。正因为返回的是同类型 Selector 列表,你可以在结果上继续调用选择方法,这就是快速选取嵌套数据的标准模式:

>>> response.css("img").xpath("@src").getall()
['image1_thumb.jpg', 'image2_thumb.jpg', 'image3_thumb.jpg',
 'image4_thumb.jpg', 'image5_thumb.jpg']

完整的"先选块、再逐块钻取"循环示例:

>>> links = response.xpath('//a[contains(@href, "image")]')
>>> links.getall()
['<a href="image1.html">Name: My image 1 <br><img src="image1_thumb.jpg" alt="image1"></a>',
 ...]

>>> for index, link in enumerate(links):
...     href_xpath = link.xpath("@href").get()
...     img_xpath = link.xpath("img/@src").get()
...     print(f"Link number {index} points to url {href_xpath!r} and image {img_xpath!r}")
...
Link number 0 points to url 'image1.html' and image 'image1_thumb.jpg'
Link number 1 points to url 'image2.html' and image 'image2_thumb.jpg'
Link number 2 points to url 'image3.html' and image 'image3_thumb.jpg'
Link number 3 points to url 'image4.html' and image 'image4_thumb.jpg'
Link number 4 points to url 'image5.html' and image 'image5_thumb.jpg'

选取元素属性:三种等价路径

拿属性值有三条路,按需选用:

1. XPath 标准语法——标准 XPath 能力,且 @attribute 可出现在 XPath 表达式的其他位置,例如按属性值过滤:

>>> response.xpath("//a/@href").getall()
['image1.html', 'image2.html', 'image3.html', 'image4.html', 'image5.html']

2. CSS 扩展 ::attr(...)

>>> response.css("a::attr(href)").getall()
['image1.html', 'image2.html', 'image3.html', 'image4.html', 'image5.html']

3. Selector 的 .attrib 属性——偏好 Python 代码查字典的人可以用它,无需写 XPath 或 CSS 扩展:

>>> [a.attrib["href"] for a in response.css("a")]
['image1.html', 'image2.html', 'image3.html', 'image4.html', 'image5.html']

.attrib 同样挂在 SelectorList 上,返回第一个匹配元素的属性字典,适合"预期只有唯一结果"的场景(按 id 选取、页面上唯一的元素):

>>> response.css("base").attrib
{'href': 'http://example.com/'}
>>> response.css("base").attrib["href"]
'http://example.com/'

# 空 SelectorList 的 .attrib 是空字典
>>> response.css("foo").attrib
{}

对照取 base URL 与图片链接的四种等价写法(XPath 的 @href、CSS 的 ::attr(href).attrib、以及含 contains() 过滤的相对路径):

>>> response.xpath("//base/@href").get()
'http://example.com/'
>>> response.css("base::attr(href)").get()
'http://example.com/'
>>> response.css("base").attrib["href"]
'http://example.com/'

>>> response.xpath('//a[contains(@href, "image")]/@href').getall()
['image1.html', 'image2.html', 'image3.html', 'image4.html', 'image5.html']
>>> response.css("a[href*=image]::attr(href)").getall()
['image1.html', 'image2.html', 'image3.html', 'image4.html', 'image5.html']

>>> response.xpath('//a[contains(@href, "image")]/img/@src').getall()
['image1_thumb.jpg', 'image2_thumb.jpg', 'image3_thumb.jpg', 'image4_thumb.jpg', 'image5_thumb.jpg']
>>> response.css("a[href*=image] img::attr(src)").getall()
['image1_thumb.jpg', 'image2_thumb.jpg', 'image3_thumb.jpg', 'image4_thumb.jpg', 'image5_thumb.jpg']

正则提取:.re() 与 .re_first()

Selector 还提供 .re() 方法用正则提取数据。注意与 .xpath()/.css() 的关键差异:.re() 直接返回字符串列表,因此无法再嵌套 .re() 调用:

>>> response.xpath('//a[contains(@href, "image")]/text()').re(r"Name:\s*(.*)")
['My image 1 ', 'My image 2 ', 'My image 3 ', 'My image 4 ', 'My image 5 ']

.get() 对应的辅助方法是 .re_first(),只取第一个匹配字符串:

>>> response.xpath('//a[contains(@href, "image")]/text()').re_first(r"Name:\s*(.*)")
'My image 1 '

旧 API 对照:extract() / extract_first() 与 get() / getall()

长期用户和大量博客教程都在用 .extract().extract_first()。文档明确:这两个方法至今仍被支持,没有任何弃用计划,但官方文档已全面改用 .get()/.getall(),因为新 API 的输出更可预测。对应关系如下:

  1. SelectorList.get()SelectorList.extract_first()
>>> response.css("a::attr(href)").get()
'image1.html'
>>> response.css("a::attr(href)").extract_first()
'image1.html'
  1. SelectorList.getall()SelectorList.extract()
>>> response.css("a::attr(href)").getall()
['image1.html', 'image2.html', 'image3.html', 'image4.html', 'image5.html']
>>> response.css("a::attr(href)").extract()
['image1.html', 'image2.html', 'image3.html', 'image4.html', 'image5.html']
  1. Selector.get()Selector.extract()
>>> response.css("a::attr(href)")[0].get()
'image1.html'
>>> response.css("a::attr(href)")[0].extract()
'image1.html'
  1. 为保持一致性,Selector 上也有 getall(),返回列表:
>>> response.css("a::attr(href)")[0].getall()
['image1.html']

总结核心差异:.get() 永远返回单个结果、.getall() 永远返回列表;而旧 .extract() 的输出到底是列表还是单个值并不总是显然,取单个结果不得不在 .extract().extract_first() 之间来回猜。

XPath 实战技巧

相对 XPath:嵌套时小心开头的斜杠

嵌套选择器时,若 XPath 以 / 开头,它是相对于整个文档的绝对路径,而不是相对于你调用它的 Selector。要提取所有 <div> 内部的 <p>,正确姿势是:

>>> divs = response.xpath("//div")

# 错误:这实际取出了整个文档的所有 <p>
for p in divs.xpath("//p"):
    print(p.get())

# 正确:注意 .//p 的点号前缀,表示相对当前上下文
for p in divs.xpath(".//p"):
    print(p.get())

# 只取直接子节点
for p in divs.xpath("p"):
    print(p.get())

更多细节可查 XPath 规范的 Location Paths 章节。

按 class 查询时优先用 CSS

按 class 选元素,标准 XPath 写法相当啰嗦:

*[contains(concat(' ', normalize-space(@class), ' '), ' someclass ')]

若偷懒写 @class='someclass' 会漏掉同时带多个 class 的元素;若改用 contains(@class, 'someclass') 又可能匹配到包含该子串的其他 class 名。既然 Scrapy 允许链式选择,通常可以直接用 CSS 按 class 选中、再切到 XPath(记得加 . 前缀):

>>> from scrapy import Selector
>>> sel = Selector(
...     text='<div class="hero shout"><time datetime="2014-07-23 19:00">Special date</time></div>'
... )
>>> sel.css(".shout").xpath("./time/@datetime").getall()
['2014-07-23 19:00']

//node[1] 与 (//node)[1] 的区别

  • //node[1]:选取各自父节点下的每个"第一个" node;
  • (//node)[1]:先选出文档中所有 node,再取其中的第一个。
>>> from scrapy import Selector
>>> sel = Selector(text="""
...     <ul class="list">
...         <li>1</li>
...         <li>2</li>
...         <li>3</li>
...     </ul>
...     <ul class="list">
...         <li>4</li>
...         <li>5</li>
...         <li>6</li>
...     </ul>""")
...
>>> xp = lambda x: sel.xpath(x).getall()

>>> xp("//li[1]")        # 各父节点下的第一个 <li>
['<li>1</li>', '<li>4</li>']
>>> xp("(//li)[1]")      # 整个文档的第一个 <li>
['<li>1</li>']
>>> xp("//ul/li[1]")      # 各 <ul> 下的第一个 <li>
['<li>1</li>', '<li>4</li>']
>>> xp("(//ul/li)[1]")    # 整个文档中 <ul> 下的第一个 <li>
['<li>1</li>']

在条件中使用文本节点:用 . 而不是 .//text()

需要把文本内容作为 XPath 字符串函数(如 contains()starts-with())的参数时,避免使用 .//text(),直接用 .。原因是:.//text() 产生的是文本元素的集合(node-set),而 node-set 转字符串时只取第一个元素的文本;相比之下,节点(node)转字符串会拼接自身及所有后代的文本。

>>> from scrapy import Selector
>>> sel = Selector(
...     text='<a href="#">Click here to go to the <strong>Next Page</strong></a>'
... )

# 观察 node-set
>>> sel.xpath("//a//text()").getall()
['Click here to go to the ', 'Next Page']
# 把 node-set 转字符串 → 只有第一个文本
>>> sel.xpath("string(//a[1]//text())").getall()
['Click here to go to the ']

# 节点转字符串 → 拼接所有后代文本
>>> sel.xpath("//a[1]").getall()
['<a href="#">Click here to go to the <strong>Next Page</strong></a>']
>>> sel.xpath("string(//a[1])").getall()
['Click here to go to the Next Page']

# 因此这样写选不到任何结果
>>> sel.xpath("//a[contains(.//text(), 'Next Page')]").getall()
[]
# 而用 . 表示当前节点就能命中
>>> sel.xpath("//a[contains(., 'Next Page')]").getall()
['<a href="#">Click here to go to the <strong>Next Page</strong></a>']

XPath 表达式中的变量

XPath 支持 $somevariable 变量引用,类似 SQL 参数化查询中的占位符。在调用 .xpath() 时通过命名参数传入绑定值:

>>> # 表达式中使用 $val,调用时传入 val=
>>> response.xpath("//div[@id=$val]/a/text()", val="images").get()
'Name: My image 1 '

# 数值同样可以作为变量
>>> response.xpath("//div[count(a)=$cnt]/@id", cnt=5).get()
'images'

所有变量引用都必须有绑定值,否则抛出 ValueError: XPath error:

移除命名空间:remove_namespaces()

抓取 XML(如 Atom/RSS feed)时,元素名常被命名空间"包裹",导致直接按名称选取失败。以 Python Insider 博客的 Atom feed 为例:

$ scrapy shell https://feeds.feedburner.com/PythonInsider

feed 开头包含多个命名空间声明(默认 http://www.w3.org/2005/Atom,以及 gd:thr: 等前缀)。进入 shell 后直接选 <link> 会得到空结果:

>>> response.xpath("//link")
[]

调用 remove_namespaces() 后,所有节点就能直接按名称访问:

>>> response.selector.remove_namespaces()
>>> response.xpath("//link")
[<Selector query='//link' data='<link rel="alternate" type="text/html" h'>,
    <Selector query='//link' data='<link rel="next" type="application/atom+'>,
    ...

文档同时解释了为什么不默认移除命名空间,按相关性排序有两条原因:

  1. 移除命名空间需要遍历并修改文档中的所有节点,对每个被抓取的文档都默认执行代价太高;
  2. 少数场景确实需要命名空间(当不同命名空间下的元素同名冲突时),虽然这种情况非常罕见。

EXSLT 扩展:正则与集合操作

由于构建在 lxml 之上,Scrapy selectors 支持部分 EXSLT 扩展,并预注册了两个可直接在 XPath 中使用的命名空间前缀:

前缀 命名空间 用途
re http://exslt.org/regular-expressions 正则表达式
set http://exslt.org/sets 集合操作

正则表达式re:test()starts-with()contains() 不够用时很实用。例:选出 class 以数字结尾的 <li> 内的链接:

>>> from scrapy import Selector
>>> doc = """
... <div>
...     <ul>
...         <li class="item-0"><a href="link1.html">first item</a></li>
...         <li class="item-1"><a href="link2.html">second item</a></li>
...         <li class="item-inactive"><a href="link3.html">third item</a></li>
...         <li class="item-1"><a href="link4.html">fourth item</a></li>
...         <li class="item-0"><a href="link5.html">fifth item</a></li>
...     </ul>
... </div>
... """
>>> sel = Selector(text=doc, type="html")
>>> sel.xpath("//li//@href").getall()
['link1.html', 'link2.html', 'link3.html', 'link4.html', 'link5.html']
>>> sel.xpath(r'//li[re:test(@class, "item-\d$")]//@href').getall()
['link1.html', 'link2.html', 'link4.html', 'link5.html']

文档附有 warning:C 库 libxslt 并不原生支持 EXSLT 正则,lxml 的实现是通过钩子回调 Python 的 re 模块,因此 XPath 中使用正则函数会带来一点性能损耗

集合操作set:difference() 等在"先排除文档树的某部分、再提取文本"时很有用。文档给出了一个从 Microdata(schema.org/Product 结构)中提取各 itemscope 与其"直属" itemprop 的完整例子:

>>> sel = Selector(text=doc, type="html")
>>> for scope in sel.xpath("//div[@itemscope]"):
...     print("current scope:", scope.xpath("@itemtype").getall())
...     props = scope.xpath("""
...                 set:difference(./descendant::*/@itemprop,
...                                .//*[@itemscope]/*/@itemprop)""")
...     print(f"    properties: {props.getall()}")
...

current scope: ['http://schema.org/Product']
    properties: ['name', 'aggregateRating', 'offers', 'description', 'review', 'review']
current scope: ['http://schema.org/AggregateRating']
    properties: ['ratingValue', 'reviewCount']
current scope: ['http://schema.org/Offer']
    properties: ['price', 'availability']
...

思路是:遍历所有 itemscope 元素,对每个作用域找出其全部后代 @itemprop,再用集合差排除那些位于其他 itemscope 内部的属性,从而得到该作用域的"直属"属性。

has-class:自定义 XPath 扩展函数

Scrapy selectors 还内置了一个 XPath 扩展函数 has-class,对"同时具备全部指定 HTML class"的节点返回 True。以下 HTML:

>>> from scrapy.http import HtmlResponse
>>> response = HtmlResponse(
...     url="http://example.com",
...     body="""
... <html>
...     <body>
...         <p class="foo bar-baz">First</p>
...         <p class="foo">Second</p>
...         <p class="bar">Third</p>
...         <p>Fourth</p>
...     </body>
... </html>
... """,
...     encoding="utf-8",
... )

用法:

>>> response.xpath('//p[has-class("foo")]')
[<Selector query='//p[has-class("foo")]' data='<p class="foo bar-baz">First</p>'>,
<Selector query='//p[has-class("foo")]' data='<p class="foo">Second</p>'>]
>>> response.xpath('//p[has-class("foo", "bar-baz")]')
[<Selector query='//p[has-class("foo", "bar-baz")]' data='<p class="foo bar-baz">First</p>'>]
>>> response.xpath('//p[has-class("foo", "bar")]')
[]

即 XPath //p[has-class("foo", "bar-baz")] 大致等价于 CSS p.foo.bar-baz。注意性能:has-class 是一个纯 Python 函数,对每个候选节点都会调用一次;而 CSS 查找会被翻译成 XPath 执行,效率更高。所以文档建议仅在 CSS 难以表达的场景下使用它。此外,parsel 提供了 set_xpathfunc 接口,可以方便地添加自己的 XPath 扩展。

Selector / SelectorList 内置方法速查

文档末尾的"Built-in Selectors reference"对应 scrapy.selector 模块SelectorSelectorList 均重导出自 unified.py,顶层 scrapy 包 也直接导出了 Selectorfrom scrapy import Selector 使用)。

Selector 对象的核心成员:

方法/属性 说明
xpath(query, **kwargs) 按 XPath 选取,返回 SelectorList;也可作为 response.xpath() 调用;支持 val= 形式的变量绑定
css(query) 按 CSS 选取(含 ::text::attr() 扩展);也可作为 response.css() 调用
jmespath(query) 按 JMESPath 选取 JSON 数据;需 parsel 1.8+,可作 response.jmespath() 调用(见 快捷方法
get(default=...) 取单个结果(旧 API 中为 extract()
getall() 返回全部结果列表(为与 SelectorList 保持一致而提供)
attrib 元素属性字典
re(pattern) / re_first(pattern) 正则提取(字符串列表 / 首个匹配)
register_namespace(prefix, uri) 注册 XPath 命名空间前缀
remove_namespaces() 移除文档全部命名空间
__bool__ 布尔求值(是否有结果)

SelectorList 对象继承 list,额外提供:xpath()css()jmespath()getall()get()re()re_first()attrib(第一个匹配元素的属性字典,空列表时为 {})。

更多示例

对 HTML 响应(sel = Selector(html_response)):

# 1. 选出所有 <h1> 元素,返回 SelectorList
sel.xpath("//h1")

# 2. 提取全部 <h1> 文本(getall 包含标签本身;text() 只取文本)
sel.xpath("//h1").getall()        # 包含 h1 标签
sel.xpath("//h1/text()").getall() # 仅文本

# 3. 遍历 <p> 并打印 class 属性
for node in sel.xpath("//p"):
    print(node.attrib["class"])

对 XML 响应(sel = Selector(xml_response)):

# 1. 选出所有 <product> 元素
sel.xpath("//product")

# 2. 需要注册命名空间才能选取带前缀的节点
sel.register_namespace("g", "http://base.google.com/ns/1.0")
sel.xpath("//g:price").getall()

此外,当前版本还支持在 HTML 与 JSON 之间混合钻取——测试文件TestJMESPath 用例演示了 resp.jmespath("html").xpath("//div/a/text()")(JSON 字段内含 HTML 再走 XPath)与 resp.xpath("//div/content/text()").jmespath("user[*].name")(HTML 节点内含 JSON 再走 JMESPath)两个方向的链式调用,且链式结果上同样可以继续使用 .re()

小结

Scrapy Selectors 的价值链条可以概括为:parsel/lxml 提供与 lxml 同级的解析速度与精度 → Scrapy 的薄封装让它与 Response 深度集成(懒加载、只解析一次、base_url 传递)→ XPath + CSS + 非标准伪元素 + EXSLT/has-class 扩展覆盖了从简单字段到 Microdata 作用域的全部提取场景。掌握本篇后,你应该能独立完成:用 response.xpath()/css() 快速迭代验证选择逻辑、用 get(default=...) 写出空值安全的字段提取、用变量绑定与 set:difference() 编写可复用的复杂 XPath,以及在处理 XML feed 时用 register_namespace()remove_namespaces() 正确应对命名空间。

登录后查看全文
热门项目推荐
相关项目推荐