一、簡介
1.下載:pip install lxml
推薦使用douban提供的pipy國內鏡像服務,如果想手動指定源,可以在pip后面跟-i 來指定源,比如用豆瓣的源來安裝web.py框架:
pip install web.py -i http://pypi.douban.com/simple --trusted-host pypi.douban.com
2.導包
from lxml import etree
3.xpath解析原理:
- 實例化一個etree對象,然后將即將被解析的頁面源碼數據加載到該對象中。
- 通過調用etree對象中的xpath方法,結合着xpath表達式進行標簽定位和數據提取
4.如何實例化一個etree對象:
將html文檔或者xml文檔轉換成一個etree對象,然后調用對象中的方法查找指定的節點
- 本地文件:將本地的一個html文檔中的數據加載到etree對象中, 使用的比較少
tree = etree.parse(文件名fileName) tree.xpath("xpath表達式")
- 網絡數據:將互聯網爬取到的頁面源碼數據加載到該對象中
tree = etree.HTML(網頁內容字符串page_text) tree.xpath("xpath表達式")
啟動和關閉插件 ctrl + shift + x

二、常用xpath表達式
首先,本地新建一個html文檔,所以要使用etree.parse(fileName)
<html lang="en"> <head> <meta charset="UTF-8" /> <title>測試bs4</title> </head> <body> <div> <p>百里守約</p> </div> <div class="song"> <p>李清照</p> <p>王安石</p> <p>蘇軾</p> <p>柳宗元</p> <a href="http://www.song.com/" title="趙匡胤" target="_self"> <span>this is span</span> 宋朝是最強大的王朝,不是軍隊的強大,而是經濟很強大,國民都很有錢</a> <a href="" class="du">總為浮雲能蔽日,長安不見使人愁</a> <img src="http://www.baidu.com/meinv.jpg" alt="" /> </div> <div class="tang"> <ul> <li><a href="http://www.baidu.com" title="qing">清明時節雨紛紛,路上行人欲斷魂,借問酒家何處有,牧童遙指杏花村</a></li> <li><a href="http://www.163.com" title="qin">秦時明月漢時關,萬里長征人未還,但使龍城飛將在,不教胡馬度陰山</a></li> <li><a href="http://www.126.com" alt="qi">岐王宅里尋常見,崔九堂前幾度聞,正是江南好風景,落花時節又逢君</a></li> <li><a href="http://www.sina.com" class="du">杜甫</a></li> <li><a href="http://www.dudu.com" class="du">杜牧</a></li> <li><b>杜小月</b></li> <li><i>度蜜月</i></li> <li><a href="http://www.haha.com" id="feng">鳳凰台上鳳凰游,鳳去台空江自流,吳宮花草埋幽徑,晉代衣冠成古丘</a></li> </ul> </div> </body></html>
頁面顯示如下

層級&索引定位
#找到class屬性值為tang的div的直系子標簽ul下的第二個子標簽li下的直系子標簽a //div[@class="tang"]/ul/li[2]/a 下面這三個結果相同 r = tree.xpath('/html/head/title') r = tree.xpath('/html//title') r = tree.xpath('//title')

r = tree.xpath('//p') # 所有的p標簽


標簽定位:
//div[@class="song"] # 找到class屬性值為song的div標簽
模糊匹配:
//div[contains(@class, "ng")] # class屬性值包含ng的div //div[starts-with(@class, "ta")] # class屬性以ta開頭的div
取屬性:
//div[@class="tang"]//li[2]/a/@href r = tree.xpath('//div[@class="song"]/img/@src') print(r)
![]()
取文本: /text()直系的文本內容 //text()所有的文本內容
//div[@class="song"]/p[1]/text() # /表示獲取某個標簽下的文本內容 //div[@class="tang"]//text() # //表示獲取某個標簽下的文本內容和所有子標簽下的文本內容
# 獲得的是列表,只不過里面只有一個元素 r = tree.xpath('//div[@class="song"]/p[4]/text()') print(r)

r = tree.xpath('//div[@class="song"]/p[4]/text()')[0] print(r)
![]()
r = tree.xpath('//div[@class="song"]//text()') print(r)


邏輯運算:
# 找到href屬性值為空且class屬性值為du的a標簽 //a[@href="" and @class="du"]
三、案例
案例1:解析圖片數據:http://pic.netbian.com/4kmeinv/
查看:網址鼠標懸浮上去會有圖片名稱,所以爬取圖片以及對應的名稱,要提前確定不是動態加載的。

import requests from lxml import etree headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36' } url = 'http://pic.netbian.com/4kdongman/' response = requests.get(url=url,headers=headers) # response.encoding = 'utf-8' #手動設定響應數據的編碼 page_text = response.text #數據解析(圖片地址,圖片名稱) tree = etree.HTML(page_text) li_list = tree.xpath('//div[@class="slist"]/ul/li') for li in li_list: #局部內容解析一定是以./開頭。etree和element都可以調用xpath img_src = 'http://pic.netbian.com'+li.xpath('./a/img/@src')[0] # 解析出來的沒有域名,要加上 img_name = li.xpath('./a/img/@alt')[0] #不要忘記前面加點號,表示從當前li標簽開始 img_name = img_name.encode('iso-8859-1').decode('gbk') #處理中文亂碼的通用形式 img_data = requests.get(url=img_src,headers=headers).content img_path = './qiutuLibs/'+img_name+'.jpg' with open(img_path,'wb') as fp: fp.write(img_data) print(img_name,'下載成功!!!')
解析:
1.
li_list = tree.xpath('//div[@class="slist"]/ul/li')
print(li_list) # 返回的是一個element類型的數據對象

2.
li標簽里面有a標簽,然后再里面是img標簽, 然后有一個src屬性和alt屬性
img_src = 'http://pic.netbian.com'+li.xpath('./a/img/@src')[0] # 解析出來的沒有域名,要加上
img_name = li.xpath('./a/img/@alt')[0]

3. 出現亂碼,有兩種解決策略:
(1)對整體設定響應數據的編碼
手動設定響應數據的編碼,查看頁面是用哪種編碼方式是utf-8,還是gbk等。如果這種方式不行,用下面的方式
response.encoding = 'utf-8'
(2)針對具體的內容手動設定
img_name = img_name.encode('iso-8859-1').decode('gbk') #處理中文亂碼的通用形式
案例2:xpath解析-boss直聘
import requests from lxml import etree import json headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36' } url = 'https://www.zhipin.com/job_detail/?query=python%E7%88%AC%E8%99%AB&city=101010100&industry=&position=' page_text = requests.get(url=url, headers=headers).text # 數據解析:jobName,salary,company,jobDesc tree = etree.HTML(page_text) li_list = tree.xpath('//div[@class="job-list"]/ul/li') job_data_list = [] for li in li_list: job_name = li.xpath('.//div[@class="info-primary"]/h3/a/div/text()')[0] # 記得后面加[0] salary = li.xpath('.//div[@class="info-primary"]/h3/a/span/text()')[0] company = li.xpath('.//div[@class="company-text"]/h3/a/text()')[0] detail_url = 'https://www.zhipin.com' + li.xpath('.//div[@class="info-primary"]/h3/a/@href')[0] # 詳情頁的頁面源碼數據 detail_page_text = requests.get(url=detail_url, headers=headers).text detail_tree = etree.HTML(detail_page_text) job_desc = detail_tree.xpath('//*[@id="main"]/div[3]/div/div[2]/div[2]/div[1]/div//text()') job_desc = ''.join(job_desc) dic = { 'job_name': job_name, 'salary': salary, 'company': company, 'job_desc': job_desc } job_data_list.append(dic) fp = open('job.json', 'w', encoding='utf-8') json.dump(job_data_list, fp, ensure_ascii=False) fp.close() print('over')


解析:
1. 因為有br標簽,所以用//

job_desc = detail_tree.xpath('//*[@id="main"]/div[3]/div/div[2]/div[2]/div[1]/div//text()') print(job_desc)
2. 輸出的是列表,里面是元素

所以,字符串拼接
job_desc = detail_tree.xpath('//*[@id="main"]/div[3]/div/div[2]/div[2]/div[1]/div//text()') job_desc = ''.join(job_desc) print(job_desc)

最終文件

案例3:xpath解析-熱門城市全國城市名稱 https://www.aqistudy.cn/historydata
import requests from lxml import etree headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36' } url = 'https://www.aqistudy.cn/historydata/' page_text = requests.get(url=url,headers=headers).text tree = etree.HTML(page_text) city_list = tree.xpath('//div[@class="bottom"]/ul/li/a/text() | //div[@class="bottom"]/ul/div[2]/li/a/text()') # 邏輯 #hot_city://div[@class="bottom"]/ul/li/a/text() #all_city://div[@class="bottom"]/ul/div[2]/li/a/text() print(city_list) print(len(city_list))


全部城市: //div[@class="bottom"]/ul/div[2]/li/a/text()


案例4:獲取好段子中段子的內容和作者http://www.haoduanzi.com
from lxml import etree import requests url='http://www.haoduanzi.com/category-10_2.html' headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36', } url_content=requests.get(url,headers=headers).text tree=etree.HTML(url_content) # 使用xpath解析從網絡上獲取的數據 title_list=tree.xpath('//div[@class="log cate10 auth1"]/h3/a/text()') # 解析獲取當頁所有段子的標題 ele_div_list=tree.xpath('//div[@class="log cate10 auth1"]') text_list=[] # 最終會存儲12個段子的文本內容 for ele in ele_div_list: text_list=ele.xpath('./div[@class="cont"]//text()') # 段子的文本內容(是存放在list列表中) text_str=str(text_list) # list列表中的文本內容全部提取到一個字符串中 text_list.append(text_str) # 字符串形式的文本內容防止到all_text列表中 print(title_list) print(text_list)
案例5:58二手房
import requests from lxml import etree url ='https://bj.58.com/shahe/ershoufang/?utm_source=market&spm=u-2d2yxv86y3v43nkddh1.BDPCPZ_BT&PGTID=0d30000c-0047-e4e6-f587-683307ca570e&ClickID=1' headers = { 'User-Agent':'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.119 Safari/537.36' } page_text = requests.get(url=url,headers=headers).text tree = etree.HTML(page_text) li_list = tree.xpath('//ul[@class="house-list-wrap"]/li') fp = open('58.csv','w',encoding='utf-8') for li in li_list: title = li.xpath('./div[2]/h2/a/text()')[0] price = li.xpath('./div[3]//text()') price = ''.join(price) fp.write(title+":"+price+'\n') fp.close() print('over')

案例6:http://pic.netbian.com/4kmeinv/
import requests from lxml import etree import os import urllib url = 'http://pic.netbian.com/4kmeinv/' headers = { 'User-Agent':'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.119 Safari/537.36' } response = requests.get(url=url,headers=headers) #response.encoding = 'utf-8' if not os.path.exists('./imgs'): os.mkdir('./imgs') page_text = response.text tree = etree.HTML(page_text) li_list = tree.xpath('//div[@class="slist"]/ul/li') for li in li_list: img_name = li.xpath('./a/b/text()')[0] #處理中文亂碼 img_name = img_name.encode('iso-8859-1').decode('gbk') img_url = 'http://pic.netbian.com'+li.xpath('./a/img/@src')[0] img_path = './imgs/'+img_name+'.jpg' urllib.request.urlretrieve(url=img_url,filename=img_path) print(img_path,'下載成功!') print('over!!!')
案例7:下載煎蛋網中的圖片數據:http://jandan.net/ooxx【重點】src加密*****
import requests from lxml import etree from fake_useragent import UserAgent import base64 import urllib.request url = 'http://jandan.net/ooxx' ua = UserAgent(verify_ssl=False,use_cache_server=False).random headers = { 'User-Agent':ua } page_text = requests.get(url=url,headers=headers).text tree = etree.HTML(page_text) #在抓包工具的數據包響應對象對應的頁面中進行xpath的編寫,而不是在瀏覽器頁面中。 #獲取了加密的圖片url數據 imgCode_list = tree.xpath('//span[@class="img-hash"]/text()') imgUrl_list = [] for url in imgCode_list: img_url = 'http:'+base64.b64decode(url).decode() #base64.b64decode(url)為byte類型,需要轉成str imgUrl_list.append(img_url) for url in imgUrl_list: filePath = url.split('/')[-1] urllib.request.urlretrieve(url=url,filename=filePath) print(filePath+'下載成功')
查看頁面源碼:發現所有圖片的src值都是一樣的。簡單觀察會發現每張圖片加載都是通過jandan_load_img(this)這個js函數實現的。在該函數后面還有一個class值為img-hash的標簽,里面存儲的是一組hash值,該值就是加密后的img地址。加密就是通過js函數實現的,所以分析js函數,獲知加密方式,然后進行解密。
通過抓包工具抓取起始url的數據包,在數據包中全局搜索js函數名(jandan_load_img),然后分析該函數實現加密的方式。在該js函數中發現有一個方法調用,該方法就是加密方式,對該方法進行搜索。搜索到的方法中會發現base64和md5等字樣,md5是不可逆的所以優先考慮使用base64解密。






案例7:爬取站長素材中的簡歷模板
import requests import random from lxml import etree headers = { 'Connection':'close', # 當請求成功后,馬上斷開該次請求(及時釋放請求池中的資源) 'User-Agent':'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.119 Safari/537.36' } url = 'http://sc.chinaz.com/jianli/free_%d.html' for page in range(1,4): # 因為第一頁和其他頁url格式不一樣,所以分情況討論 if page == 1: new_url = 'http://sc.chinaz.com/jianli/free.html' else: new_url = format(url%page) response = requests.get(url=new_url,headers=headers) response.encoding = 'utf-8' # 中文亂碼,先調整編碼方式 page_text = response.text tree = etree.HTML(page_text) div_list = tree.xpath('//div[@id="container"]/div') for div in div_list: detail_url = div.xpath('./a/@href')[0] name = div.xpath('./a/img/@alt')[0] detail_page = requests.get(url=detail_url,headers=headers).text tree = etree.HTML(detail_page) download_list = tree.xpath('//div[@class="clearfix mt20 downlist"]/ul/li/a/@href') # 這樣獲得的是每個的所有下載鏈接 download_url = random.choice(download_list) # 為了防止每個鏈接因請求過於頻繁被禁,隨機選擇一個 data = requests.get(url=download_url,headers=headers).content fileName = name+'.rar' with open(fileName,'wb') as fp: fp.write(data) print(fileName,'下載成功')
Alt里面的圖片名稱是中文,要注意打印看一下會不會有亂碼

有亂碼,嘗試用第一種方式是否可以解決,可以解決就不用第二種方式
詳情頁中每個li標簽對應一個下載地址
li標簽里有一個a

