跳转至

01 playwright 定位方法

用过playwright的都知道,这个包最重要的就是需要理清楚你要获取的数据,或者需要操作的元素的具体位置,这个位置可以通过 id 给,可以通过类名给,也可以通过xpath语法给,所以本文重点讲一下这些用法如何去做表示

在提定位之前,还是先把几个主要用到的动作说一下,因为实际过程中反反复复用的也就这么几个

  • click() 方法,点击页面的某个元素,如按钮
  • fill()方法,向元素中填入对应的内容,比如文本框或者表单
  • inner_text()方法,获取元素内部文本,当然如果通过定位获取了多个,可以用all_inner_texts()整合成一个列表

一、通过CSS样式或者Tag定位

在界面按下F12,就可以查看页面的html结构了,由于这个页面代码比较复杂,因此在接下来的介绍中,我只会重点关注什么属性,用什么表示方式

在定位中,我们常常使用locator方法,后面写上表达式

  1. 如果定位的是唯一的id,那用#进行表示,如:page.locator("#item")
  2. 如果定位的是类名class,则用.进行表示,如:page.locator(".item")
  3. 如果定位的是tag,则直接不加任何符号,只填名字,如page.locator("item")

如果要获取获取到的所有符合的内容,我们使用all()方法,写作page.locator(".item").all()其返回的是一个列表,每个元素如下,只是nth不同,而如果需要看的是个数,则使用count() 方法

<Locator frame=<Frame name= url='https://movie.douban.com/top250'> selector='.item >> nth=0'>

同时我们也可以使用firstlastnth(下标从0开始)来获取单个元素:

1
2
3
# 打印第一个和最后一个元素的文本内容
print(result.first.inner_text(),result.last.inner_text())
print(result.nth(10).inner_text()) # 打印第11个元素的文本内容

二、通过元素之间的对应关系匹配

在页面中我们经常看到这样的结构,一个大的div套若干小元素,关键就在于找一个有代表性的,比如我们通过id定位,因为id是唯一的,所以我们就可以继续往内层找。

那比如我们需要获取标题,就可以先找到这个id=content的盒子,然后往下找h1,这样子就能很好地解析页面结构。

result = page.locator("#content")
h1_title = result.locator("h1").inner_text()
其实上面的这个还可以写成一句话,如下,这里的>代表的是直系子元素,也就是父元素的直接下级:

# 提取#content下直接子元素h1元素的文本内容
h1_title = page.locator("#content>h1").inner_text()
当然对于上图而言的所有div,有的是非直接下级,有些是直接下级,那这种情况下如果需要打印所有的,用空格隔开

1
2
3
# 提取#content下所有div元素的文本内容
texts = page.locator("#content div").all_inner_texts()
print(texts)
另外还有一种组选择的语法,用,隔开,代表若干种情况都成立,我都选取,是或者的关系,如下:

1
2
3
4
# 提取所有class为"item"或"title"的元素文本
texts = page.locator(".item,.title").all_inner_texts() 
# 提取id为"item"的元素下的所有a标签和span标签的文本
texts = page.locator("#item a, #item span").all_inner_texts()
还有对兄弟节点的选取,用+隔开(指的是紧接着的下一个兄弟节点),用~连接(指的是平级的所有兄弟节点)

1
2
3
4
# 提取类型为p,紧跟着p的下一个兄弟节点span元素的文本内容
text = page.locator("p + span").inner_text()
# 提取类型为p,紧跟着p的所有后续兄弟节点span元素的文本内容
text = page.locator("p ~ span").inner_text()

因此我们归纳总结一下,直接下级用>连接,所有下级(含直接下级和非直接下级)用若干个空格隔开就行,而对于组选择,则用,进行分割,兄弟节点用+或者~进行连接,详细结合上述注释进行理解。

另外关于父子元素的关系,还有很多其他的用法,我罗列如下以供参考:

  • nth-child(n)(下标从1开始): 父节点的第n个子节点(什么类型都可以p,span,a,div等等)
  • nth-last-child(n)(下标从1开始): 父节点倒数的第n个子节点(什么类型都可以p,span,a,div等等)
  • nth-of-type(n)(下标从1开始): 父节点的第n个子type节点(要满足和提取的type类型相同)
  • nth-last-of-type(n)(下标从1开始): 父节点的倒数第n个子type节点(要满足和提取的type类型相同)
# 提取类型为span,在所有类型子节点中(含有p,span,div等)的第一个子元素的文本内容
text = page.locator("span:nth-child(1)").inner_text()
# 提取类型为span,在所有类型子节点中(含有p,span,div等)的最后一个子元素的文本内容
text = page.locator("span:nth-last-child(1)").inner_text()
# 提取类型为span,在所有类型子节点中(含有p,span,div等)的偶数位置的子元素的文本内容
text = page.locator("span:nth-child(even)").inner_text()
# 提取类型为span,在所有类型子节点中(含有p,span,div等)的奇数位置的子元素的文本内容
text = page.locator("span:nth-child(odd)").inner_text()


# 提取类型为span,在span类型中为父节点的第1个span元素的文本内容
text = page.locator("span:nth-of-type(1)").inner_text()
# 提取类型为span,在span类型中为父节点的最后一个span元素的文本内容
text = page.locator("span:nth-last-of-type(1)").inner_text()
# 提取类型为span,在span类型中为父节点的偶数位置的span元素的文本内容
text = page.locator("span:nth-of-type(even)").inner_text()
# 提取类型为span,在span类型中为父节点的奇数位置的span元素的文本内容
text = page.locator("span:nth-of-type(odd)").inner_text()

三、通过元素的特征定位

在每个tag后面,都会跟类似classhref 这样的元素特征,我们可以通过[ ]进行标注,里面写上“元素=某特征”即可,当然除了完全的相等,还有以下几种不同的方式:

  • *= :代表元素特征只要包含某个字符串就进行提取
  • ^= :代表元素特征只要开头是某个字符串就进行提取
  • $= :代表元素特征只要以某个字符串结尾就进行提取

具体可以参考以下的代码段,上面的这些语法就有点类似正则表达式的感觉了,由于这种定位方式一般用的不多,简要提一下

1
2
3
4
5
6
7
text = page.locator("[href='https://www.douban.com/about']").inner_text()
# text = page.locator("a[href='https://www.douban.com/about']").inner_text() # 也可以多加限定符a,div等
texts = page.locator("[href*='douban']").all_inner_texts() # 提取所有包含"douban"的链接文本
texts = page.locator("[href^='https']").all_inner_texts() # 提取所有以"https"开头的链接文本
texts = page.locator("[href$='.html']").all_inner_texts() # 提取所有以".html"结尾的链接文本
# 提取div元素class为"item"且href以".html"结尾的文本,中间不能有空格
text = page.locator("div[class='item'][href$='.html']").inner_text() 


四、通过其他方式进行定位

4.1 使用XPATH进行定位

首先用的最多的就是xpath语法,尤其是在某些复杂的情况下,其能够实现更有效的更精准的定位,而且现在基本上不需要自己写xpath表达式了,都是通过浏览器拓展插件实现,比如Xpath Finder,那么我们要用xpath的话,可以这么写:

text = page.locator("xpath = /html/body/div[3]/div[1]/div/div[1]/ol/li[1]/div/div[2]/div[2]/p[1]").inner_text()

只需要指定一下是使用的方法是xpath即可

4.2 根据文本进行定位

其实上面讲到的playwright用法都是通过页面的html代码或者css特征定位元素,但是在实际界面中,用户是看不到的,所以其提供了更加方便的方式,从用户的角度出发,比如通过文本定位get_by_text,可以定位页面上包含指定字符串的元素

# 提取所有文本内容中包含"导演"的元素的文本内容
texts = page.get_by_text("导演").all_inner_texts()

也可以结合正则表达式,实现更智能的提取,比如下面这个例子

# 提取所有元素中结尾是"评价"的文本内容
texts = page.get_by_text(re.compile("评价$")).all_inner_texts()

4.3 根据Role进行定位

另外playwright也提供了根据元素功能进行定位的功能,下面仅仅只展示了三种不同的Role,标题,列表项和链接,如果需要更加具体,也可以搭配name给定,关于更多的Role,可以在 这个界面(点击跳转)进行查看

1
2
3
4
5
6
7
8
# 提取所有标题元素的文本内容
texts = page.get_by_role("heading").all_inner_texts()
# 提取所有列表项元素的文本内容
texts = page.get_by_role("listitem").all_inner_texts()
# 提取所有链接元素的文本内容
texts = page.get_by_role("link").all_inner_texts()
# 提取所有元素中包含"更多"的链接元素的文本内容
texts = page.get_by_role("link",name="更多").all_inner_texts()
注意这里name如果是指的linkdiv等的具体内容,对应的是可见文本,只要可见文本中包含关键词即可,如果要强制要求一模一样,就在get_by_role中多增加一个参数叫做exact=True,而如果name如果是指的img的内容,则往往对应CSS属性中图片元素的alt值,这个可以稍微记一下

在实际过程中,由于Role种类太多,因此一般还是借助codegen生成,不会自己去写,这样方便些

还有一些其他的不常用,但是codegen在记录的时候会产生的,我们这里简单提一下:

  • get_by_placeholder方式,一般是根据input的文本框的占位文字定位
  • get_by_alt_text方式,一般是有alt属性的,根据对应的alt值进行定位
  • get_by_title方式,一般是有title属性的,根据对应的title值进行定位

1
2
3
page.get_by_placeholder("搜索电影、电视剧、综艺、影人",exact=True).fill("肖申克的救赎")
page.get_by_alt_text("搜索").click()
page.get_by_title("更多").click()
这些用的频次不算很高,其实只要掌握了基本的CSS定位方法以及XPATH,配合正则表达式,基本上都能将你想要的信息提取出来,希望上面的解释能给你提供有价值的参考