01. XPath

  • 参考W3School XPath语法手册:https://www.w3school.com.cn/xpath/index.asp

    1.1 文本提取技术

    1.1.1 文本提取技术概述

  • 文本提取技术即从一段文本中提取出想要内容的技术。

  • 在爬虫中使用文本技术,必须要保证需要的数据存在于文本中。
    • 一般来说Re正则和XPath都用来在HTML源码中提取数据。
    • 所以前提就要保证你提取的数据在HTML中存在。
  • 如何判断数据在网页源代码中:在网页中右键 >> 查看网页源代码,若在这里找得到需要的数据,就可以用文本提取技术爬取。
  • 若所需要的信息在网页源代码中不存在,则可以直接放弃文本提取技术,使用网络接口或者Selenium程序的方式进行爬虫。

    1.1.2 正则表达式

  • 正则表达式是一种特殊的字符串,是一款常用于校验、筛选、检索、替换字符串内容的工具。

  • 正则表达式有其自己独特的语法,以及处理引擎,在所有编码语言中几乎都存在。(具体的语法在01. Python与SQL — 01. Python语法 — 06. Python高级 — 03. 正则表达式中有详细讲解)
  • 正则表达式可以用在爬虫的文本匹配与提取中,但是不推荐,主要是因为正则表达式存在以下一些缺点:

    • 缺点1:写起来麻烦
    • 缺点2:由于浏览器功能强大,当HTML非正常编写时,浏览器也能正常解析,如下面的代码明显缺少了四个</li>

      1. <ul>
      2. <li>AAAA
      3. <li>BBBB
      4. <li>CCCC
      5. <li>DDDD
      6. </ul>
      • 但是浏览器照样可以正常显示无序列表的效果。

image.png

  • 缺点3:HTML中有一些特殊字符,比如HTML中需要用&nbsp;来表示空格,因此<p>aa aa</p>在浏览器中显示的结果是aa aa,但是源码中的内容依旧是aa aa。这种情况也会为正则的匹配制造很多麻烦。

    1.1.3 HTML、XML、XPath介绍

  • ML:Markup Language,标记性语言。
  • HTML:Hyper Text Markup Language,超文本标记语言。
    • HTML主要用于网页骨架设计上。
    • 其标签都是内置的,如<p><img><a>等。
    • HTML的标签有单标签,也有双标签。
  • XML:Extensible Markup Language,可扩展标记语言。
    • XML主要用于标记数据、定义数据类型等场景。
    • 其标签都是自定义的,如<hehe><haha>等都可以。
    • XML比较严格,其标签必须是双标签。
  • XPath(XML Path Language,XML路径语言),是一种专门用于从XML和HTML中提取内容的语言。

    1.2 XPath结构

    1.2.1 元素树

  • 在01. 爬虫前置基础知识 — 02. 爬虫前端知识基础 — 3.9.2 Document对象中提到,DOM模型会将一个HTML文档转换成一个有树形层级关系的对象进行管理。

image.png

  • 在XPath中也采用这样的思想将一个完整的HTML/XML文件转换成一颗元素树(也可称文档树、标签树)。

    1.2.2 节点(Node)

  • 在XPath中有七种类型的节点:元素、属性、文本、命名空间、处理指令、注释以及文档(根)节点。

  • XML文档是被作为节点树来对待的,树的根被称为文档节点或者根节点。
  • 以下面这个XML文档为例: ```xml <?xml version=”1.0” encoding=”ISO-8859-1”?>

J K. Rowling 2005 29.99

  1. - 上面的XML文档中的节点例子:
  2. - `<bookstore>`:文档节点。
  3. - `<author>J K. Rowling</author>`:元素节点。
  4. - `lang="en"`:属性节点。
  5. <a name="v5Zzw"></a>
  6. #### 1.2.3 基本值(亦称原子值,Atomic Value)
  7. - 基本值指的是无父或无子的节点。
  8. - 1.2.2中的`J K. Rowling``en`都没有子节点,属于基本值。
  9. <a name="FH3cN"></a>
  10. #### 1.2.4 项目(Item)
  11. - 项目指的是基本值或者节点。
  12. <a name="CzgPq"></a>
  13. ### 1.3 节点关系
  14. <a name="bl8vw"></a>
  15. #### 1.3.1 父(Parent)
  16. - 每个元素节点(非文档节点的元素标签)以及属性节点都有一个父节点。
  17. - 如下XML中,titleauthoryearprice的父是book
  18. ```xml
  19. <book>
  20. <title>Harry Potter</title>
  21. <author>J K. Rowling</author>
  22. <year>2005</year>
  23. <price>29.99</price>
  24. </book>

1.3.2 子(Children)

  • 父与子是相对的关系,如3.3.1中的title、author、year、price都是book的子。
  • 元素节点可有零个、一个或多个子。

    1.3.3 同胞(Sibling)

  • 同胞节点也可称为兄弟节点,指的是具有相同父的节点。

  • 如3.3.1中title、author、year、price的父都是book,因此认为title、author、year、price四个是同胞节点。

    1.3.4 先辈(Ancestor)

  • 一个节点的父节点、父节点的父节点、父节点的父节点的父节点、……,都称为是这个节点的先辈节点。

  • 如title的先辈节点是book元素节点和bookstore元素节点

    1. <bookstore>
    2. <book>
    3. <title>Harry Potter</title>
    4. <author>J K. Rowling</author>
    5. <year>2005</year>
    6. <price>29.99</price>
    7. </book>
    8. </bookstore>

    1.3.5 后代(Descendant)

  • 一个节点的子节点、子节点的子节点、子节点的子节点的子节点、……,都称为是这个节点的后代节点。

  • 可以说后代与先辈是一个相对的概念。
  • 如1.3.4中的book元素节点、title元素节点、author元素节点、year元素节点、price元素节点都是bookstore元素节点的后代。

    1.4 XPath语法

    1.4.1 XPath提取内容的方式

  • XPath使用路径表达式来选取HTML/XML文档中的节点或者节点集。

  • 节点是通过沿着路径(Path)或者步(Steps)来选取的。

    1.4.2 实验数据

  • 本小节的所有示例将使用以下这个XML文档进行。 ```xml <?xml version=”1.0” encoding=”ISO-8859-1”?>

29.99 39.95

  1. <a name="b31wY"></a>
  2. #### 1.4.3 选取节点
  3. - `nodename`:采用节点名称的方式可以选取此节点中的所有子节点。
  4. - 示例:选取bookstore的所有子节点,即两个book节点。
  5. ```xml
  6. bookstore
  • /:从根节点选取。(假如路径以斜杠/开头,则代表到某元素的绝对路径)
  • 示例1:选取根元素bookstore。

    1. /bookstore
  • 示例2:选取属于bookstore的子元素中的book元素。

    1. bookstore/book
  • //:从匹配选择的当前节点选择文档中的节点,而不考虑它们的位置。(简单来说就是全局搜索指定的节点)

  • 示例1:搜索文档中所有的book子元素,而不考虑它们在文档中的位置。(即全局搜索book)

    1. //book
  • 示例2:选择bookstore元素的后代的所有book元素,不管它们位于bookstore之下的什么位置。

    1. bookstore//book
  • @:选取属性。

  • 示例:选取名为lang的所有属性。

    1. //@lang
  • .:选取当前节点。

  • ..:选取当前节点的父节点。(即上一级节点)

    1.4.4 谓语

  • 谓语用来查找某个特定的节点或者包含某个指定的值的节点,谓语被嵌在方括号中。

  • 选择属于bookstore子元素的第一个book元素:/bookstore/book[1]
  • 选择属于bookstore子元素的最后一个book元素:/bookstore/book[last()]
  • 选择属于bookstore子元素的倒数第二个book元素:/bookstore/book[last()-1]
  • 选择属于bookstore子元素的最前面的两个book元素:/bookstore/book[position()<3]
  • 选择拥有lang属性的title元素://title[@lang]
  • 选择拥有lang属性,并且值为eng的title元素://title[@lang='eng']
  • 选择属于bookstore子元素的所有book元素,但要求price元素的值必须大于35.00:/bookstore/book[price>35.00]
  • 选择属于bookstore子元素的所有book元素中的所有title元素,但要求price元素的值必须大于35.00:/bookstore/book[price>35.00]/title

    1.4.5 选取未知节点

  • XPath通配符可用来选取未知的XML元素。

  • *:匹配任何元素节点。
  • 示例1:选取bookstore元素的所有子元素。

    1. /bookstore/*
  • 示例2:选取文档中的所有元素。

    1. //*
  • @*:匹配任何属性节点。

  • 示例:选取所有带有属性的title元素。

    1. //title[@*]
  • node():匹配任何类型的节点。

    1.4.6 选取若干路径

  • 通过在路径表达式中使用或运算符|,来实现若干路径的选择。

  • 选取book子元素中的所有title元素和price元素://book/title | //book/price
  • 选取文档中所有的title元素和所有的price元素://title | //price
  • 选取属于bookstore子元素的book元素中的所有title元素,以及文档中的price元素:/bookstore/book/title | //price

    1.4.7 函数

  • XPath的函数其实很少会被使用,但是text()contains()使用的还是比较多的。

  • text()函数:
    • 在XPath表达式的末尾写上/text()即可从节点中提取出值。
    • 选取出book子元素中的所有price元素的值://book/price/text()
    • 注意:一个XPath可能取回来的是一类数据,因此lxml模块获取到的可能是一个列表,因此处理值需要处理列表中的内容。
  • contains()函数:

    • contains(A, B)用于检测A中是否包含B。
    • 示例://div[contains(@class, "learn")]可以查找class中包含learn的div节点。

      1.5 XPath with Python

      1.5.1 lxml库安装

  • 在Python中使用XPath需要结合lxml这个XPath格式解析模块完成。

  • lxml模块可以通过pip命令安装。

    1. pip install lxml

    1.5.2 构建元素树

  • 首先,要导入lxml模块中的etree,然后通过etree.HTML(HTML文本)构建一个元素树。

  • 代码实现:用requests获取懂车帝(https://www.dongchedi.com/)页面的源码,然后构建成一颗文档树。 ```python import requests from lxml import etree

resp = requests.get(“https://www.dongchedi.com/“) page_source = resp.text

tree = etree.HTML(page_source) print(tree) #

  1. <a name="cVZT9"></a>
  2. #### 1.5.3 xpath()规则匹配
  3. - `元素树对象.xpath('XPath表达式')`可以根据XPath表达式从HTML/XML文档中匹配提取需要的内容。
  4. - `xpath()`中的XPath表达式字符串建议使用单引号`'`。
  5. - 因为在XPath表达式中可能出现双引号`"`。
  6. - 示例:编写XPath,从阿凡达:水之道的短评页面(https://movie.douban.com/subject/4811774/comments)中爬取数据。
  7. - 首先打开阿凡达:水之道的短评页面,然后右键 >> 查看网页源代码,检查一下评论数据是否存在网页源代码中。
  8. ![image.png](https://cdn.nlark.com/yuque/0/2023/png/2692415/1673616078704-ff82f7ad-d504-489f-8876-f68287dae16c.png#averageHue=%23fcfaf9&clientId=u4ec3bcd2-8b92-4&from=paste&height=162&id=u23b24ef5&originHeight=226&originWidth=769&originalType=binary&ratio=1&rotation=0&showTitle=false&size=15127&status=done&style=none&taskId=u1f576b51-9cd0-4ff9-9101-b3284af9367&title=&width=550)
  9. - 从搜索结果中可以看出,评论存在与网页HTML的源代码中,因此可以使用XPath提取。
  10. - 接着为了方便获取,可以将网页源代码放到IDE中格式化一下,方便查看网页结构以编写XPath。
  11. - 分析得出,评论数据的XPath为:`//span[@class="short"]/text()`,即从文档中查到带有short类的span节点。
  12. - 最后,编码获取数据。
  13. ```python
  14. # 导入模块
  15. import requests
  16. from lxml import etree
  17. # 构建请求
  18. url = "https://movie.douban.com/subject/4811774/comments"
  19. headers = {
  20. "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36"
  21. }
  22. resp = requests.get(url=url, headers=headers)
  23. # 获取网页源代码,构建元素树
  24. page_source = resp.text
  25. tree = etree.HTML(page_source)
  26. # 用XPath提取评论数据
  27. comments = tree.xpath('//span[@class="short"]/text()')
  28. for comment in comments:
  29. print(comment)

1.6 XPath辅助工具

1.6.1 Copy XPath

image.png

  • 在需要的元素的位置上右键 >> 检查,即可在Element中定位到代码块。

image.png

  • 然后右键代码块 >> Copy >> Copy XPath即可得到目标元素对应的XPath,如这里是://*[@id="__next"]/section/main/div/div[3]/div[1]/div/div[2]/div[1]/a/span

    1.6.2 XPath Helper

  • XPath Helper是个浏览器扩展程序,Google Chrome地址为:https://chrome.google.com/webstore/detail/xpath-helper/hgimnogjllphhhkhlmebbmlgjoejdpjl?hl=zh-CN

  • XPath Helper主要用来验证XPath的正确性,还是以新片场广告页面为例。
  • 点击浏览器右上角的扩展程序列表,点击其中的中的XPath Helper。

image.png

  • 此时在页面顶部就会出现QUERY和RESULTS两个窗口。

image.png

  • 在QUERY中输入XPath表达式(这里就以3.6.1中Copy XPath得到的为例),此时在RESULT中即可得到匹配的结果,并且匹配的元素会在页面中高亮显示。

image.png

02. 新片场爬虫

2.1 项目描述

  • 项目根页面:新片场广告页面(https://www.xinpianchang.com/discover/article-1-0-all-all-0-0-hot?from=navigator
  • 需求:

    • 爬取新片场广告页面第一页所有视频的基础信息,包括:广告标题、点赞量、收藏量、分享量、播放量。
    • 爬虫每一个视频的播放链接。(可能存在多个清晰度版本,爬取其中任意一个即可)
    • 爬取每一个视频下的评论信息。

      2.2 爬取基本信息

      2.2.1 获取详情页链接

  • 该项目需要爬取的信息大多都是在广告的详情页中,因此需要先获取所有广告的详情页链接。

  • 从UI的角度上看,点击广告页面每个广告的标题,就可以进入详情页。这就意味着这个标题中存在一个<a>标签,它的href中存储着详情页的地址。

image.png

  • 因此获取详情页链接的程序实现思路为:
    • 请求新片场广告页面地址,根据相应的内容构建XPath元素树。
    • 编写相应的XPath规则获取详情页地址。
  • 代码实现: ```python import requests from lxml import etree

发送GET请求,构建元素树。

resp = requests.get(“https://www.xinpianchang.com/discover/article-1-0-all-all-0-0-hot?from=navigator“) tree = etree.HTML(resp.text)

编写XPath规则,获取详情页地址。

movies_element = tree.xpath(‘//div[@id=”__next”]//main/div[1]/div[3]/div’) # 所有广告的集合。 for movie_element in movies_element:

  1. # 遍历每个广告,获取详情页链接。
  2. detail_url = movie_element.xpath('.//div[@class="relative"]/a/@href')[0]
  3. print(detail_url)
  1. <a name="SgtZN"></a>
  2. #### 2.2.2 爬取基本信息(单个)
  3. - 广告的广告标题、点赞量、收藏量、分享量、播放量实际上都在广告的详情页中。
  4. - 以中国山河,很有橙意!([https://www.xinpianchang.com/a12316708](https://www.xinpianchang.com/a12316708))这则广告为例:
  5. ![image.png](https://cdn.nlark.com/yuque/0/2023/png/2692415/1677378592889-fb601da3-95e7-4464-95fe-25ebb6a2a6f5.png#averageHue=%23faeded&clientId=ubd4fc9b6-94d3-4&from=paste&height=249&id=uc32f449b&originHeight=338&originWidth=678&originalType=binary&ratio=1&rotation=0&showTitle=false&size=44132&status=done&style=none&taskId=u9fa238cd-ac8d-49b3-a427-b0b444fe7f1&title=&width=500)
  6. - 这些数据经过分析,发现在网页源代码中都可以找到,因此都可以使用XPath提取。
  7. - 故爬取这部分数据的思路即为:
  8. - 请求详情页链接,通过响应构建元素树。
  9. - 编写XPath规则提取数据。
  10. - 代码实现:
  11. ```python
  12. import requests
  13. from lxml import etree
  14. # 请求《中国山河,很有橙意!》的详情页链接,构建元素树。
  15. sub_page_resp = requests.get("https://www.xinpianchang.com/a12316708")
  16. sub_page_tree = etree.HTML(sub_page_resp.text)
  17. # 通过XPath获取目标数据。
  18. title = sub_page_tree.xpath('//*[@id="__next"]/section/main/section[2]/section/main/section/header/div[1]/h2/text()')[0] # 广告标题
  19. likes = sub_page_tree.xpath('//*[@id="__next"]/section/main/section[2]/section/main/section/header/div[3]/div/button[1]/span/text()')[0] # 点赞量
  20. collection = sub_page_tree.xpath('//*[@id="__next"]/section/main/section[2]/section/main/section/header/div[3]/div/button[2]/span/text()')[0] # 收藏量
  21. share = sub_page_tree.xpath('//*[@id="__next"]/section/main/section[2]/section/main/section/header/div[3]/div/button[3]/span/text()')[0] # 分享量
  22. playback = sub_page_tree.xpath('//*[@id="__next"]/section/main/section[2]/section/main/section/header/div[2]/div[1]/span[2]/text()')[0] # 播放量
  23. # 输出结果。
  24. print([title, likes, collection, share, playback]) # ['中国山河,很有橙意!(中国平安 x 星球研究所)', '1.1k', '2.2k', '136', '13.8w']

2.2.3 爬取基本信息(所有)

  • 2.2.2中爬取的是单个详情页的数据,要爬取所有详情页的数据实际上只需要请求所有详情页,然后构建元素树,编写XPath即可。
  • 实际上就是2.2.1和2.2.2两部分代码的整合。 ```python import requests from lxml import etree

对广告页面发送GET请求,构建元素树。

resp = requests.get(“https://www.xinpianchang.com/discover/article-1-0-all-all-0-0-hot?from=navigator“) tree = etree.HTML(resp.text)

获取详情页链接,请求详情页构建元素树并通过XPath爬取数据。

movies_element = tree.xpath(‘//*[@id=”__next”]//main/div[1]/div[3]/div’) for movie_element in movies_element: detail_url = movie_element.xpath(‘.//div[@class=”relative”]/a/@href’)[0]

  1. # 防止数据爬取过程中出现异常。
  2. # 通过try-except结构增加程序的健壮性。
  3. try:
  4. # 构建详情页元素树。
  5. detail_page_resp = requests.get(detail_url)
  6. detail_page_tree = etree.HTML(detail_page_resp.text)
  7. # 爬取基本信息。
  8. title = detail_page_tree.xpath('//*[@id="__next"]/section/main/section[2]/section/main/section/header/div[1]/h2/text()')[0] # 广告标题
  9. likes = detail_page_tree.xpath('//*[@id="__next"]/section/main/section[2]/section/main/section/header/div[3]/div/button[1]/span/text()')[0] # 点赞量
  10. collection = detail_page_tree.xpath('//*[@id="__next"]/section/main/section[2]/section/main/section/header/div[3]/div/button[2]/span/text()')[0] # 收藏量
  11. share = detail_page_tree.xpath('//*[@id="__next"]/section/main/section[2]/section/main/section/header/div[3]/div/button[3]/span/text()')[0] # 分享量
  12. playback = detail_page_tree.xpath('//*[@id="__next"]/section/main/section[2]/section/main/section/header/div[2]/div[1]/span[2]/text()')[0] # 播放量
  13. print([title, likes, collection, share, playback])
  14. except Exception as e:
  15. pass
  1. <a name="QuKS6"></a>
  2. #### 2.2.4 基本信息存储
  3. - 当前项目的数据是标准的二维数据,可以考虑使用Excel、CSV、数据库等方式存储。
  4. - 但最方便的存储方式就是使用CSV存储。
  5. ```python
  6. import requests
  7. import csv
  8. from lxml import etree
  9. # 对广告页面发送GET请求,构建元素树。
  10. resp = requests.get("https://www.xinpianchang.com/discover/article-1-0-all-all-0-0-hot?from=navigator")
  11. tree = etree.HTML(resp.text)
  12. # 获取详情页链接,请求详情页构建元素树并通过XPath爬取数据。
  13. datas = [] # 用于存储所有数据。
  14. movies_element = tree.xpath('//*[@id="__next"]//main/div[1]/div[3]/div')
  15. for movie_element in movies_element:
  16. detail_url = movie_element.xpath('.//div[@class="relative"]/a/@href')[0]
  17. # 防止数据爬取过程中出现异常。
  18. # 通过try-except结构增加程序的健壮性。
  19. try:
  20. # 构建详情页元素树。
  21. detail_page_resp = requests.get(detail_url)
  22. detail_page_tree = etree.HTML(detail_page_resp.text)
  23. # 爬取基本信息。
  24. title = detail_page_tree.xpath('//*[@id="__next"]/section/main/section[2]/section/main/section/header/div[1]/h2/text()')[0] # 广告标题
  25. likes = detail_page_tree.xpath('//*[@id="__next"]/section/main/section[2]/section/main/section/header/div[3]/div/button[1]/span/text()')[0] # 点赞量
  26. collection = detail_page_tree.xpath('//*[@id="__next"]/section/main/section[2]/section/main/section/header/div[3]/div/button[2]/span/text()')[0] # 收藏量
  27. share = detail_page_tree.xpath('//*[@id="__next"]/section/main/section[2]/section/main/section/header/div[3]/div/button[3]/span/text()')[0] # 分享量
  28. playback = detail_page_tree.xpath('//*[@id="__next"]/section/main/section[2]/section/main/section/header/div[2]/div[1]/span[2]/text()')[0] # 播放量
  29. # 将数据封装成一个列表,然后添加到datas中。
  30. data = [title, likes, collection, share, playback]
  31. datas.append(data)
  32. print(data)
  33. except Exception as e:
  34. pass
  35. # 数据持久化
  36. with open('./movies_base_info.csv', 'w', encoding='utf-8', newline='') as file:
  37. handle = csv.writer(file)
  38. handle.writerow(['广告标题', '点赞量', '收藏量', '分享量', '播放量'])
  39. handle.writerows(datas)

2.3 爬取播放链接

2.3.1 爬取播放地址(单个)

  • 以中国山河,很有橙意!(https://www.xinpianchang.com/a12316708)这则广告为例(不一定要是这个,在新片场广告页面中随机选取一个即可),审查它的Elements可以发现视频的播放链接存在于<video>标签中,但是在网页源代码中却找不到<video>标签,这就表明视频是一个动态加载的数据。

Elements中的<video>标签在网页源代码中搜索不到<video>标签

  • 因此需要分析Fetch/XHR中的内容,最后在Fetch/XHR中的名为POpZQXYnP517Er5a?appKey=61a2f329348b3bf77&extend=userInfo%2CuserStatus的资源中发现了相关数据。

image.png

POpZQXYnP517Er5a
61a2f329348b3bf77

  • 因此,这个程序可以分两步实现:
    • 首先,获取POpZQXYnP517Er5a?appKey=61a2f329348b3bf77&extend=userInfo%2CuserStatus这个资源的Request URL地址。
    • 然后请求Request URL,得到的响应是个JSON。解析这个JSON,即可得到广告视频的播放地址。 ```python import json import requests from lxml import etree

请求详情页,构建元素树。

detail_resp = requests.get(‘https://www.xinpianchang.com/a12316708‘) detail_tree = etree.HTML(detail_resp.text)