Xpath简介
XPath
是一门可以在XML文档中查找指定信息的语言。可以参考w3cschool的XPath教程,内容简单易懂。
XPath
使用路径表达式来选取 XML 文档中的节点或者节点集,非常像电脑文件系统中的文件路径。
选取节点时特别注意 //
表示递归查找 、@
表示选取属性
在选取节点的时候可以添加谓语(Predicates
)来选取某个特定的节点或者包含某个特定的值的节点,这个是非常重要的,基本上爬虫都会用谓语
选取指定节点。
XPath
的轴可以定义相对于当前节点的节点集,通俗来说就是获取类似祖先
、孙子
之类的节点。(亦可参考深入浅出XPath轴)
轴名称 | 结果 |
---|---|
parent | 父节点, 俗称爸爸, 只有一个 |
child | 子节点, 俗称孩子 |
ancestor | 祖先节点, 爸爸*n, 特别注意 爸爸的兄弟不算 |
descendant | 子孙节点, 孩子*n, 特别注意 弟弟的孩子不算 |
following | 自你以下页面中所有节点, 即同胞弟弟节点即其后代, 和descendant的区别就是包括弟弟的后代 |
following-sibling | 同层下节点, 即同胞弟弟节点, 所有的弟弟 |
preceding | 自你以上页面中所有节点, 即同胞哥哥节点即其后代, 和ancestor的区别就是包括哥哥的后代 |
preceding-sibling | 同层上节点, 即同胞哥哥节点, 所有的哥哥 |
XPath
轴选取可以参考下面的示例代码,通过selenium
+XPath
轴选取爬取京东
首页指定内容。
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from time import sleep
options = webdriver.ChromeOptions()
driver = webdriver.Chrome(service=Service(r"E:\chromeDriver\chromedriver.exe"), options=options)
url = "https://www.jd.com/"
driver.get(url)
# 1.获取左边的分类栏目
# 有data-index属性 且 class='cate_menu_item'
l = driver.find_elements(By.XPATH, "//li[@data-index][@class='cate_menu_item']")
print("\n".join([x.text for x in l]))
# 2. 获取第3个分类 和 获取倒数第二个分类 和 获取前五个分类 和 获取后五个分类
l = driver.find_elements(By.XPATH, "//li[@data-index][@class='cate_menu_item'][3]")
l = driver.find_elements(By.XPATH, "//li[@data-index][@class='cate_menu_item'][last()-1]")
l = driver.find_elements(By.XPATH, "//li[@data-index][@class='cate_menu_item'][position()<=5]")
l = driver.find_elements(By.XPATH, "//li[@data-index][@class='cate_menu_item'][position()>(last()-5)]")
# 2.1 Xpath 逻辑运算符 and or
# 3. Xpath轴运算
# 参考地址 https://www.bbsmax.com/A/rV57PwWWdP/
# 装饰字画 的链接地址
driver.find_element(By.XPATH, "//a[text()='装饰字画']").get_property("href")
# parent::dd 上层父节点叫dd的亲生爸爸, 只有一个
driver.find_element(By.XPATH, "//a[text()='装饰字画']/parent::dd").get_attribute("clstag")
# child::a 下层所有子节点,所有亲儿子中叫a的;
l=driver.find_elements(By.XPATH, "//dd[@clstag='h|keycount|head|category_04d04']/child::a")
print("\n".join([x.get_property("innerText") for x in l]))
# ancestor:: dl 上面所有直系节点,是你亲生爸爸或者你亲爹或者你亲爹的爸爸中叫div的. 爸爸的兄弟是不算的, 必须是 爸爸*n
driver.find_element(By.XPATH, "//a[text()='装饰字画']/ancestor::dl").get_attribute("class")
driver.find_element(By.XPATH, "//a[text()='装饰字画']/ancestor::dd").get_attribute("clstag")
# descendant::li 下面所有节点,你的后代中叫div的,不包括你弟弟的后代, 必须是 儿子*n
l=driver.find_elements(By.XPATH, "//div[@id='J_cate']/descendant::li")
print("\n".join([x.get_property("innerText") for x in l]))
# following::li 自你以下页面中所有节点叫div的. 和descendant的区别就是包括弟弟的后代
driver.find_elements(By.XPATH, "//div[@id='J_cate']/following::li")
# following-sibling::li 同层下节点,你所有的亲弟弟中叫li的
l = driver.find_elements(By.XPATH, "//li[@data-index=2][@class='cate_menu_item']/following-sibling::li")
print("\n".join([x.get_property("innerText") for x in l]))
# preceding::a 同层上节点,你所有的亲哥哥以及他们的后代中叫a的
driver.find_elements(By.XPATH, "//li[@data-index=2][@class='cate_menu_item']/preceding::li")
# preceding-sibling::li 同层上节点,你所有的亲哥哥中叫li的;
l = driver.find_elements(By.XPATH, "//li[@data-index=5][@class='cate_menu_item']/preceding-sibling::li")
print("\n".join([x.get_property("innerText") for x in l]))
常用的XPath
函数就是下面几种,所有的XPath
函数可以参考XPath函数指南
# 常用函数
contains(string1,string2)
starts-with(string1,string2)
ends-with(string1,string2)
not(arg) 取反
lxml模块
python的lxml
模块可以通过XPath解析网页,最常用的就是xpath
方法,不建议使用find
方法(因为该方法没有完全实现 xpath 的规则)
示例代码如下。
from lxml import etree
import requests
url = "https://www.jd.com/"
# 请求网址
resp = requests.get(url)
# 将网址的文本内容变成一个 HTML 对象
# 类型是 lxml.etree._Element, 只需掌握该对象的 xpath 方法即可
html = etree.HTML(resp.text)
# xpath 获取分类对象
l = html.xpath("//li[@data-index][@class='cate_menu_item']")
for tmp in l:
child = tmp.findall("a")
print(" / ".join([x.text for x in child]))
# 直接获取文本内容
# 但是这个就没有层级关系
l = html.xpath("//li[@data-index][@class='cate_menu_item']/a/text()")
print(l)
# lxml.etree._Element 类常用方法
# xpath 方法, 以当前对象作为 context node 来计算xpath对应的节点. 用它即可.
# findall 方法, 可以传入 tag 或 xpath 不建议使用
# getchildren 方法, 获取所有孩子节点
特别注意通过xpath
方法获取的是list
列表对象。
Xpath玩转豆瓣电影TOP10
# -*- coding:UTF-8 -*-
"""
获取 豆瓣电影 Top-250
"""
import re
import requests
from lxml import etree
import importlib,sys
# sys.setdefaultencoding("utf-8")
importlib.reload(sys)
from time import sleep
from random import randint
headers = {
"user-agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.45 Safari/537.36",
# 设置不保持连接防止服务器屏蔽 或者 每个请求完成后都关闭 keep-alive 或者 close
"Connection": "close",
"Host": "movie.douban.com"
}
url_f = "https://movie.douban.com/top250?start=%s&filter="
def spider(start, end):
result_list = list()
for i in range(start, end+1):
url = url_f % (25*(i-1),)
resp = requests.get(url, headers=headers)
html = etree.HTML(resp.text)
# 获取当前页面所有的 电影
movies = html.xpath("//div[@id='content']//li")
# 遍历 movies 获取想要的数据
for movie in movies:
info = list()
# 获取名字和别名
movie_name_tag_a = movie.xpath(".//div[@class='hd']/a")[0]
# 添加名字
movie_name_text = movie_name_tag_a.xpath("span/text()")
info.append(movie_name_text[0])
alias = " | ".join([x.strip("/\xa0") for i, x in enumerate(movie_name_text) if i > 0])
# 添加别名
info.append(alias)
# 添加图片地址
info.append(movie.xpath(".//div[@class='pic']/a/img/@src")[0])
# 添加导演和主演
director_actor = movie.xpath(".//div[@class='bd']/p[1]")[0].text
director = re.search("导演: (.*?)\xa0", director_actor).group(1) if "\xa0" in director_actor else re.search("导演: (.*)", director_actor).group(1)
if re.search("主演: (.*)", director_actor):
actor = re.search("主演: (.*)", director_actor).group(1)
else:
actor = ""
info.append(director)
info.append(actor)
# 添加评分
info.append(movie.xpath(".//span[@class='rating_num']/text()")[0])
# 添加点评人数
dianping = movie.xpath(".//span[not(@*)]/text()")[0]
info.append(re.search("(\d+)人评价", dianping).group(1))
# 添加简介
if movie.xpath(".//p[@class='quote']/span/text()"):
info.append(movie.xpath(".//p[@class='quote']/span/text()")[0])
else:
info.append("")
print("\t".join(info))
sleep(randint(1,3))
titles = ["名字", "别名", "图片", "导演", "主演", "评分", "评价人数", "简介"]
Xpath获取猪八戒网站信息
# -*- coding:UTF-8 -*-
"""
获取 猪八戒网 信息
"""
import requests
from lxml import etree
from lxml.etree import _Element
url = "https://foshan.zbj.com/search/service/?l=0&kw=java%E5%90%8E%E5%8F%B0&r=2"
# 请求网页内容
resp = requests.get(url)
# 看看网页内容是否正常
# print(resp.text)
# 用lxml将网页解析成 lxml.etree._Element 对象
html = etree.HTML(resp.text)
print("\t".join(["店名", "简介", "价格", "评分", "销量", "好评"]))
# 找到所有的服务商
shop_list = html.xpath('//div[@class="search-result-list-service"]/div[@data-styleonly]') # type: list[_Element]
for shop in shop_list:
# 店名 由于xpath返回的都是列表, 所以这里取第一个
shop_name = shop.xpath('.//div[contains(@class,"shop-info")]/text()')[0]
# 其中 find 方法没有完全实现 xpath 的规则, 所以这里就报错了, 不建议使用 find 方法
# shop_name = shop.find(".//div[contains(@class,'shop-info')]/text()")
# 简介
shop_brief = shop.xpath('.//div[contains(@class,"name-pic-box")]/a/text()')[0]
# 价格
shop_price = shop.xpath('.//div[contains(@class,"price")]/span/text()')[0]
# 评分
shop_scores = shop.xpath('.//div[contains(@class,"fraction")]//span/text()')
# 为了适配没有评分的情况
shop_score = shop_scores[0] if shop_scores else "0"
# 销量
shop_sales = shop.xpath('.//div[contains(@class,"sales")]//span[2]/text()')[0]
# 好评
shop_evaluate = shop.xpath('.//div[contains(@class,"evaluate")]//span[2]/text()')[0]
print("\t".join([shop_name, shop_brief, shop_price, shop_score, shop_sales, shop_evaluate]))