response.css() 和 response.xpath() 到底谁在执行?::text 与 //text() 差在哪?坏掉的 HTML 被修成什么样?为什么有些 css 选择器特别慢?这篇从翻译后的 XPath 讲起,再量一遍 1 MB 文档上的耗时。
parsel 1.12.1 / lxml (6, 1, 3, 0) / libxml2 2.14.6 / cssselect 1.6.0
div.item > a → descendant-or-self::div[@class and contains(@class, 'item') and contains(concat(' ', normalize-space(@class), ' '), ' item ')]/a
ul li:nth-child(2) → descendant-or-self::ul/descendant::li[count(preceding-sibling::*) = 1]
a[href^="http"] → descendant-or-self::a[@href and starts-with(@href, 'http')]
p::text → descendant-or-self::p/text()
a::attr(href) → descendant-or-self::a/@href
li:first-child → descendant-or-self::li[count(preceding-sibling::*) = 0]
div#main p.x → descendant-or-self::div[@id = 'main']/descendant::p[@class and contains(@class, 'x') and contains(concat(' ', normalize-space(@class), ' '), ' x ')]
a:not(.skip) → descendant-or-self::a[not(@class and contains(@class, 'skip') and contains(concat(' ', normalize-space(@class), ' '), ' skip '))]
h1 + p → descendant-or-self::h1/following-sibling::*[(self::p) and (position() = 1)]
input[type=checkbox]:checked → descendant-or-self::input[(@type = 'checkbox') and ((@selected and name(.) = 'option') or (@checked and (name(.) = 'input' or name(.) = 'command')and (@type = 'checkbox' or @type = 'radio')))]parsel 的 Selector 用 lxml 解析并执行 XPath;css() 经 cssselect 翻译成 XPath 之后执行,所以 css 不比 xpath 快。
div::text = ['head ', ' tail '] div ::text = ['head ', 'bold', ' tail ', 'it'] //div/text() = ['head ', ' tail '] //div//text() = ['head ', 'bold', ' tail ', 'it'] string(//div) = ['head bold tail it'] div *::text = ['head ', 'bold', ' tail ', 'it'] div.get() = <div id="a">head <b>bold</b> tail <i>it</i></div> div::attr(id) = a | @id: a 不存在时 .get() = None | .get(default="N/A") = N/A | .getall() = [] re / re_first = ['head'] it nodefault
::text 只取直接子文本;要所有后代文本写 ::text 前加空格或用 //text();要拼接成一个字符串用 string()。
输入: '<p>one<p>two<table><tr><td>a<td>b</table><b><i>x</b></i>&unknown; & <div' lxml 修复后的 HTML: <html><body><p>one</p><p>two</p><table><tr><td>a</td><td>b</td></tr></table><b><i>x</i></b>&unknown; & </body></html> p 的个数: 2 ;td 的个数: 2 type="xml" 也不抛错(lxml 用 recover 模式),修复后: <p>one<p>two<table><tr><td>a<td>b</td><b><i>x</i></b>&unknown; <div/></td></tr></table></p></p> type="xml" 合法文档: ['1', '2'] 空文档 Selector(text="") 的 root: <Element html at 0x…> ;get(): '<html></html>'
libxml2 的 HTML 解析器会补全、理顺标签,所以选择器几乎不会因为 HTML 写得烂而失败;type="xml" 同样是容错模式但修复结果不同。
//entry : 0 个
//a:entry (namespaces={"a": …}) : 2 个
remove_namespaces() 之后 //entry : 2 个;//link/@href = ['/a', '/b']XML 带默认命名空间时普通 XPath 查不到元素,需要给前缀或 remove_namespaces()。
文档大小 1,077,815 字节
Selector(text=html) 解析:15.8 ms
s.css("li.item a::attr(href)").getall():178.0 ms
s.xpath("//li[@class='item']/a/@href").getall():28.9 ms
s.xpath("//a/@href").getall():23.5 ms
s.css("li:nth-child(100)").get():1203.4 ms
s.css("li")(只构造 Selector 列表,不取值):16.9 ms
s.re(r"/p/(\d+)")(对整个文档做正则):10.2 ms
HtmlResponse.css 第一次(含解析):53.7 ms;同一个 response 第二次起:37.92 ms(response.selector 已缓存)解析很快(1 MB 约 16 ms),慢在选择器本身:类选择器翻译出来的 XPath 比 @class 等值判断慢数倍,:nth-child(n) 在大量兄弟上是平方级。解析结果缓存在 response.selector 里。