使用CSS Selector进行网抓

Power BI Desktop 5月更新来了,在网抓方面增加了一个新功能———使用示例提取表。
使用前需在设置-预览功能中,勾选"新的通过Web体验"。

该功能类似于智能填充以及添加示例中的列,允许用户手动输入目标数据,并根据输入内容智能分析转换规则,获取到网页中具有同样样式的其他数据。
但从初步体验来看,明显还不够智能,测试了几个稍微复杂点的网页都抓不到数据,所以不应太过依赖可视化界面操作。

该功能的背后则是5月新增的两个函数Web.BrowserContents以及Html.Table
其中前者用于获取网页的Html源码,初步探究其作用相当于Text.FromBinaryWeb.Contents的组合,第二参数的用法还需进一步测试。后者能够返回包含指定CSS Selector所有内容的表,是实现CSS Selector网抓的核心函数。
这两个函数目前只能在PBID中使用,预计在一个月内会更新到Excel中的Power Query。

CSS Selector是用于定位网页中元素的一种常见方法,其他的方法还有Xpath等。
如果把网页比作房子,Html相当于房子的骨架,用于设定网页的结构和内容;CSS是层叠样式表,用于控制网页中的样式,相当于房子的外观,比如墙刷什么颜色,地板用什么材质等等;JS相当于家电,比如空调、洗衣机等等,用于给房子增加一些功能。
Html.Table出现之前,如果要抓的网页为非结构化数据,即不包含表、Json或Xml,要定位到某个值只能将获取网页源代码后当成文本处理,既复杂又不准确。
而有了这个函数,就能够根据网页中特定的样式,定位到指定位置的值。
用法主要有两种:

1、抓取不同网页中同一位置的值

比如已知商品的URL,要批量抓取商品售价。

实现如下的效果:

对于不同网页中的不同商品,虽然售价的数字不同,但是使用的颜色、字体等样式都是相同的,所以可以根据用来描述售价的CSS Selector来定位,从而抓取不同网页中同一位置的值。
首先以iPhone X的网页为例,URL为https://item.jd.com/5089253.html,在浏览器中右击红色的售价数字,选择检查,调出调试窗口。窗口中自动定位并高亮了所选位置的源码,右击源码,选择Copy-Copy selector。

然后进入查询编辑器构建函数,Web.BrowserContents("http://item.jd.com/5089253.html")获取网页源代码;Html.Table第一参数为源代码,第二参数为大的list中套小list,每一个小list表示一列,包含列名及CSS Selector,把刚才复制的内容粘贴进去即可得到单个网页中的售价。

let
    源 = Html.Table(Web.BrowserContents("https://item.jd.com/5089253.html"),
    	{
            {"售价",
                 "body > div:nth-child(9) > div > div.itemInfo-wrap > div.summary.summary-first > div > div.summary-price.J-summary-price > div.dd > span.p-price > span.price.J-p-5089253"
             }
        })
in
    源

由于其他网页中的售价也是同样的样式,所以同样的公式应该就能批量获取到所有网页中的售价。但是如果真的这么做,会发现其他网址中的售价都为空,结果不正确。
再回头来分析下刚才复制的CSS Selector,内容为:
body > div:nth-child(9) > div > div.itemInfo-wrap > div.summary.summary-first > div > div.summary-price.J-summary-price > div.dd > span.p-price > span.price.J-p-5089253
结构大致为A.a > B.b > C.c,具有多个节点,表示具有a属性的A标签下具有b属性的B标签下具有c属性的C标签。
再复制其他网页中的CSS Selector进行对比,发现前面都是一样的,只有最后一个节点的span.price.J-p-5089253不同。
而在前一个节点span.p-price下具有两个节点,一个用来描述人民币符号,另一个用来描述数字。

所以可以把最后一个节点中不同的属性删除,写成span.price,同样可以获取到数字"8316"。也可以直接把最后一个节点删除,仅定位前一个节点,获取结果为"¥8316"。
复制的完整CSS Selector非常长,而填入函数中的参数通常可以简化。
比如在这个网页中,仅用span.p-price这一个节点足以定位,只需要确保描述的唯一性即可,代码为= Table.AddColumn(源, "售价", each Html.Table(Web.BrowserContents([网址]),{{"售价","span.p-price"}})[售价]{0})

2、抓取同一网页中不同位置的同属性值

比如在iPhone X的网页中,具有公开版、原厂延保版、双网通版等多个版本,这些值使用了同样的样式,则可使用CSS Selector批量抓取。
首先选择其中任一版本复制CSS Selector,比如复制"蓝牙二级套装",内容为#choose-attr-2 > div.dd > div:nth-child(5) > a
注意不要选择网页中默认框选的版本,因为样式不同。
可多复制几个进行对比,发现只有倒数第二个节点不同。
div:nth-child(5),表示父元素div中的第5个子元素,现在是要获取所有子元素,把该节点中的属性删除即可,改为#choose-attr-2 > div.dd > div > a
代码为= Html.Table(Web.BrowserContents("https://item.jd.com/5089253.html"),{{"版本","#choose-attr-2 > div.dd > div > a"}})

有关CSS Selector的更多技巧可自行百度。

附件下载

11 Replies to “使用CSS Selector进行网抓”

  1. 已拜读,非常感谢!也用该法测试了一些网页,非常好用,只要有“span class”这种结构的都可以采集到。
    但是有两点疑问想请教大神:
    1.该法适合只适合于详情页?因为如果页面中有一些相同样式的元素(比如搜索结果页等列表页),就只能采集到第一个内容了
    2.当元素的样式名称中含有空格的时候,在M语言中是否有空格的表达式?

    1. 那就没办法用本文介绍的方法了,只能Text.FromBinary(Web.Contents())获取源码,然后当文本处理,Text.BetweenDelimiters提取首尾分隔符之间的文本

    2. 可以用{"Link","a",each [Attributes][href]},其中Link是结果表的列名称,"a"是用于定位href所在的元素的css标记,最后一部分即从元素中提取href属性的值。

  2. 老师您好,能讲一下这一部分吗?
    Table.FromRows(Json.Document(Binary.Decompress(Binary.FromText("fc1BDoMgEEDRqxjWBhjGgdFTuGxiWFkTbKQ2gd6/K91Q2L+fvyxin8P53rqH6EXI+ZMmpfa8Rfl6yvWMijSPhlCGHA/h+9tzNx/f1Ircn6jl7Vh415wAaAJXRhWPgMgAhbepdUFnLKMuq4ofiC3T5f0P", BinaryEncoding.Base64), Compression.Deflate)), let _t = ((type text) meta [Serialized.Text = true]) in type table [型号 = _t, 网址 = _t])

  3. 请问老师该函数能抓取需登录网站数据吗,该如何设置,是否如web.contents,参考了语法,好像没有说。

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注