Python爬蟲 | lxml解析html頁面

一、簡介

1.下載:pip install lxml

推薦使用douban提供的pipy國內(nèi)鏡像服務(wù),如果想手動指定源,可以在pip后面跟-i 來指定源,比如用豆瓣的源來安裝web.py框架:

pip install web.py -i http://pypi.douban.com/simple --trusted-host pypi.douban.com

2.導(dǎo)包

from lxml import etree

3.xpath解析原理:

  • 實(shí)例化一個(gè)etree對象,然后將即將被解析的頁面源碼數(shù)據(jù)加載到該對象中。
  • 通過調(diào)用etree對象中的xpath方法,結(jié)合著xpath表達(dá)式進(jìn)行標(biāo)簽定位和數(shù)據(jù)提取

4.如何實(shí)例化一個(gè)etree對象:

將html文檔或者xml文檔轉(zhuǎn)換成一個(gè)etree對象,然后調(diào)用對象中的方法查找指定的節(jié)點(diǎn)

  • 本地文件:將本地的一個(gè)html文檔中的數(shù)據(jù)加載到etree對象中, 使用的比較少
tree = etree.parse(文件名fileName)
tree.xpath("xpath表達(dá)式")
  • 網(wǎng)絡(luò)數(shù)據(jù):將互聯(lián)網(wǎng)爬取到的頁面源碼數(shù)據(jù)加載到該對象中
tree = etree.HTML(網(wǎng)頁內(nèi)容字符串page_text)
tree.xpath("xpath表達(dá)式")

啟動和關(guān)閉插件 ctrl + shift + x

二、常用xpath表達(dá)式

首先,本地新建一個(gè)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  title="趙匡胤" target="_self">
            <span>this is span</span>
        宋朝是最強(qiáng)大的王朝,不是軍隊(duì)的強(qiáng)大,而是經(jīng)濟(jì)很強(qiáng)大,國民都很有錢</a>
        <a href="" class="du">總為浮云能蔽日,長安不見使人愁</a>
        <img src="http://www.baidu.com/meinv.jpg" alt="" />
    </div>

    <div class="tang">
        <ul>
            <li><a  title="qing">清明時(shí)節(jié)雨紛紛,路上行人欲斷魂,借問酒家何處有,牧童遙指杏花村</a></li>
            <li><a  title="qin">秦時(shí)明月漢時(shí)關(guān),萬里長征人未還,但使龍城飛將在,不教胡馬度陰山</a></li>
            <li><a  alt="qi">岐王宅里尋常見,崔九堂前幾度聞,正是江南好風(fēng)景,落花時(shí)節(jié)又逢君</a></li>
            <li><a  class="du">杜甫</a></li>
            <li><a  class="du">杜牧</a></li>
            <li><b>杜小月</b></li>
            <li><i>度蜜月</i></li>
            <li><a  id="feng">鳳凰臺上鳳凰游,鳳去臺空江自流,吳宮花草埋幽徑,晉代衣冠成古丘</a></li>
        </ul>
    </div>
</body></html>

頁面顯示如下

e

層級&索引定位

#找到class屬性值為tang的div的直系子標(biāo)簽ul下的第二個(gè)子標(biāo)簽li下的直系子標(biāo)簽a
//div[@class="tang"]/ul/li[2]/a

下面這三個(gè)結(jié)果相同
r = tree.xpath('/html/head/title')
r = tree.xpath('/html//title')
r = tree.xpath('//title')
r = tree.xpath('//p')            # 所有的p標(biāo)簽

標(biāo)簽定位:

//div[@class="song"]     # 找到class屬性值為song的div標(biāo)簽 

模糊匹配:

//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()直系的文本內(nèi)容 //text()所有的文本內(nèi)容

//div[@class="song"]/p[1]/text()     # /表示獲取某個(gè)標(biāo)簽下的文本內(nèi)容
//div[@class="tang"]//text()         # //表示獲取某個(gè)標(biāo)簽下的文本內(nèi)容和所有子標(biāo)簽下的文本內(nèi)容
# 獲得的是列表,只不過里面只有一個(gè)元素
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)

邏輯運(yùn)算

# 找到href屬性值為空且class屬性值為du的a標(biāo)簽
//a[@href="" and @class="du"]

三、案例

案例1:解析圖片數(shù)據(jù):http://pic.netbian.com/4kmeinv/

查看:網(wǎng)址鼠標(biāo)懸浮上去會有圖片名稱,所以爬取圖片以及對應(yīng)的名稱,要提前確定不是動態(tài)加載的。

image
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'                                           #手動設(shè)定響應(yīng)數(shù)據(jù)的編碼


page_text = response.text

#數(shù)據(jù)解析(圖片地址,圖片名稱)    
tree = etree.HTML(page_text)
li_list = tree.xpath('//div[@class="slist"]/ul/li')        

for li in li_list:

    #局部內(nèi)容解析一定是以./開頭。etree和element都可以調(diào)用xpath
    img_src = 'http://pic.netbian.com'+li.xpath('./a/img/@src')[0]      # 解析出來的沒有域名,要加上
    img_name = li.xpath('./a/img/@alt')[0]                              #不要忘記前面加點(diǎn)號,表示從當(dāng)前l(fā)i標(biāo)簽開始
    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)                                    # 返回的是一個(gè)element類型的數(shù)據(jù)對象

** 2.**

li標(biāo)簽里面有a標(biāo)簽,然后再里面是img標(biāo)簽, 然后有一個(gè)src屬性和alt屬性

img_src = 'http://pic.netbian.com'+li.xpath('./a/img/@src')[0]                    # 解析出來的沒有域名,要加上
img_name = li.xpath('./a/img/@alt')[0]    
image

3. 出現(xiàn)亂碼,有兩種解決策略:

(1)對整體設(shè)定響應(yīng)數(shù)據(jù)的編碼

手動設(shè)定響應(yīng)數(shù)據(jù)的編碼,查看頁面是用哪種編碼方式是utf-8,還是gbk等。如果這種方式不行,用下面的方式

response.encoding = 'utf-8'

(2)針對具體的內(nèi)容手動設(shè)定

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

# 數(shù)據(jù)解析: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]

    # 詳情頁的頁面源碼數(shù)據(jù)
    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. 因?yàn)橛衎r標(biāo)簽,所以用//

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:獲取好段子中段子的內(nèi)容和作者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解析從網(wǎng)絡(luò)上獲取的數(shù)據(jù)
title_list=tree.xpath('//div[@class="log cate10 auth1"]/h3/a/text()')   # 解析獲取當(dāng)頁所有段子的標(biāo)題

ele_div_list=tree.xpath('//div[@class="log cate10 auth1"]')

text_list=[]                                                            # 最終會存儲12個(gè)段子的文本內(nèi)容
for ele in ele_div_list:
    text_list=ele.xpath('./div[@class="cont"]//text()')                 # 段子的文本內(nèi)容(是存放在list列表中)
    text_str=str(text_list)                                             # list列表中的文本內(nèi)容全部提取到一個(gè)字符串中
    text_list.append(text_str)                                          # 字符串形式的文本內(nèi)容防止到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:下載煎蛋網(wǎng)中的圖片數(shù)據(jù):http://jandan.net/ooxx【重點(diǎn)】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)        


#在抓包工具的數(shù)據(jù)包響應(yīng)對象對應(yīng)的頁面中進(jìn)行xpath的編寫,而不是在瀏覽器頁面中。
#獲取了加密的圖片url數(shù)據(jù)
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類型,需要轉(zhuǎn)成str
imgUrl_list.append(img_url)

for url in imgUrl_list:
    filePath = url.split('/')[-1]
    urllib.request.urlretrieve(url=url,filename=filePath)
    print(filePath+'下載成功')

案例8:爬取站長素材中的簡歷模板

import requests
import random
from lxml import etree
headers = {
    'Connection':'close',                             # 當(dāng)請求成功后,馬上斷開該次請求(及時(shí)釋放請求池中的資源)
    '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):                            # 因?yàn)榈谝豁摵推渌搖rl格式不一樣,所以分情況討論    
    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'                        # 中文亂碼,先調(diào)整編碼方式
    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')    # 這樣獲得的是每個(gè)的所有下載鏈接
        download_url = random.choice(download_list)             # 為了防止每個(gè)鏈接因請求過于頻繁被禁,隨機(jī)選擇一個(gè)
        data = requests.get(url=download_url,headers=headers).content
        fileName = name+'.rar'
        with open(fileName,'wb') as fp:
            fp.write(data)
            print(fileName,'下載成功')

Alt里面的圖片名稱是中文,要注意打印看一下會不會有亂碼

image

有亂碼,嘗試用第一種方式是否可以解決,可以解決就不用第二種方式

詳情頁中每個(gè)li標(biāo)簽對應(yīng)一個(gè)下載地址

li標(biāo)簽里有一個(gè)a

image
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容