Scrapy Selectors 完全指南:用 XPath、CSS 与 EXSLT 扩展高效提取网页数据
网页抓取中最核心的任务,就是从 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 的输出更可预测。对应关系如下:
SelectorList.get()≡SelectorList.extract_first():
>>> response.css("a::attr(href)").get()
'image1.html'
>>> response.css("a::attr(href)").extract_first()
'image1.html'
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']
Selector.get()≡Selector.extract():
>>> response.css("a::attr(href)")[0].get()
'image1.html'
>>> response.css("a::attr(href)")[0].extract()
'image1.html'
- 为保持一致性,
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+'>,
...
文档同时解释了为什么不默认移除命名空间,按相关性排序有两条原因:
- 移除命名空间需要遍历并修改文档中的所有节点,对每个被抓取的文档都默认执行代价太高;
- 少数场景确实需要命名空间(当不同命名空间下的元素同名冲突时),虽然这种情况非常罕见。
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 模块(Selector、SelectorList 均重导出自 unified.py,顶层 scrapy 包 也直接导出了 Selector 供 from 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() 正确应对命名空间。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00