Python爬蟲-數據解析學習筆記之xpath

1驱负、xpath學習筆記

1)xpath描述

??xpath(XML Path Language)是一門在XML和HTML文檔中查找信息的語言,可用來在XML和HTML文檔中對元素和屬性進行遍歷寝贡。

2)xpaht開發(fā)工具

??A)Chrome插件XPath Helper
??B)Firefox插件Try XPath

3)xpath語法

選取節(jié)點:xpath使用路徑表達式來選取XML文檔中的節(jié)點或節(jié)點集悼吱。
??A)當/在最前面時沸停,選取根節(jié)點下面的元素堰酿,否則選取某個節(jié)點下面的元素疾宏,然后寫標簽名,再寫謂詞進行選取触创,示例代碼如下:
??/div[@class = 'abc']
??/div/tr[@class = 'xyz']
??B)使用//選取整個頁面當中的元素坎藐,不管在任何位置都是,然后寫標簽名哼绑,再寫謂詞進行選取岩馍,示例代碼如下:
??//div[@class = 'abc']
??//div//tr[@class = 'xyz']
??C)使用@選取某個節(jié)點的屬性,使用中括號[]括起來抖韩,示例代碼如下:
??//div[@class = 'abc']
??D)使用.指定當前節(jié)點蛀恩,示例代碼如下:
??./div[@class = 'abc']

謂詞用法:謂詞用來查找某個特定的節(jié)點或包含某個指定的值的節(jié)點,被嵌在中括號[]中帽蝶。
??A)選取某個節(jié)點下的第一個節(jié)點元素赦肋,示例代碼如下:
??//div//tr[1]
??B)選取某個節(jié)點下的倒數第二個節(jié)點元素块攒,示例代碼如下:
??//div//tr[last()]
??C)選取某個節(jié)點下的前面兩個節(jié)點元素励稳,示例代碼如下:
??//div//tr[position() < 3]
??D)選取某個節(jié)點下?lián)碛心硞€屬性的節(jié)點元素,示例代碼如下:
??//div//tr[@class]
??E)選取某個節(jié)點下等于某個值的所有屬性的節(jié)點元素囱井,示例代碼如下:
??//div//tr[@class = 'abc]

通配符:使用*代表通配符驹尼。
??A)使用*匹配任意節(jié)點,示例代碼如下:
??//div/*
??B)使用@*匹配節(jié)點中的任意屬性庞呕,示例代碼如下:
??//div//tr[@*]

選取多個路徑:通過在路徑表達式中使用|運算符新翎,可以選取若干個路徑程帕,示例代碼如下:
??//div//tr[last()] | //div//td[@class = 'abc] | //div//p

需要注意的知識點
??1、///的區(qū)別:/代表只獲取直接子節(jié)點地啰,//代表獲取子孫節(jié)點愁拭。一般//用的比較多,當然也要視情況而定亏吝。
??2岭埠、contains:有時候某個屬性中包含了多個值,那么可以使用contains()函數蔚鸥,示例代碼如下:
??//div[contains(@class, 'job_detail')]
??3惜论、謂詞中的下標是從1開始的,不是從0開始的止喷。

2馆类、lxml學習筆記

1)lxml描述

??lxml 是 一個HTML/XML的解析器,主要的功能是如何解析和提取 HTML/XML 數據弹谁。
??lxml和正則一樣乾巧,也是用 C 實現(xiàn)的,是一款高性能的 Python HTML/XML 解析器僵闯,可以使用之前學習的XPath語法卧抗,來快速的定位特定元素以及節(jié)點信息。
??lxml Python 官方文檔:http://lxml.de/index.html鳖粟。

2)解析HTML字符串

??使用lxml.etree.HTML進行解析社裆,示例代碼如下:

htmlElement = etree.HTML(text)
print(etree.tostring(htmlElement, encoding = 'utf-8').decode('utf-8'))
3)解析HTML文件

??使用lxml.etree.parse進行解析,示例代碼如下:

htmlElement = etree.HTML('tencent.html')
print(etree.tostring(htmlElement, encoding = 'utf-8').decode('utf-8'))

??這個函數默認使用的是XML解析器向图,所以如果碰到一些不規(guī)范的HTML代碼的時候就會解析錯誤泳秀,這時候就要自己創(chuàng)建HTML解析器,示例代碼如下:

parser = etree.HTMLParser(encoding = 'utf-8')
htmlElement = etree.parse('lagou.html', parser = parser)
print(etree.tostring(htmlElement, encoding = 'utf-8').decode('utf-8'))

3榄攀、lxml與xpath結合

1)獲取所有li標簽
 from lxml import etree
 html = etree.parse('hello.html')
 print type(html)  # 顯示etree.parse() 返回類型
 result = html.xpath('//li')
 print(result)  # 打印<li>標簽的元素集合
2)獲取所有l(wèi)i元素下的所有class屬性的值
 from lxml import etree
 html = etree.parse('hello.html')
 result = html.xpath('//li/@class')
 print(result)
3)獲取li標簽下href為www.baidu.com的a標簽
 from lxml import etree
 html = etree.parse('hello.html')
 result = html.xpath('//li/a[@href="www.baidu.com"]')
 print(result)
4)獲取li標簽下所有span標簽
 from lxml import etree
 html = etree.parse('hello.html')
 #result = html.xpath('//li/span')
 #注意這么寫是不對的:
 #因為 / 是用來獲取子元素的嗜傅,而 <span> 并不是 <li> 的子元素,所以檩赢,要用雙斜杠
 result = html.xpath('//li//span')
 print(result)
5)獲取li標簽下的a標簽里的所有class
 from lxml import etree
 html = etree.parse('hello.html')
 result = html.xpath('//li/a//@class')
 print(result)
6)獲取最后一個li的a的href屬性對應的值
 from lxml import etree
 html = etree.parse('hello.html')
 result = html.xpath('//li[last()]/a/@href')
 # 謂語 [last()] 可以找到最后一個元素
 print(result)
7)獲取倒數第二個li元素的內容
 from lxml import etree
 html = etree.parse('hello.html')
 result = html.xpath('//li[last()-1]/a')
 # text 方法可以獲取元素內容
 print(result[0].text)
8)獲取倒數第二個li元素的內容的第二種方式
 from lxml import etree
 html = etree.parse('hello.html')
 result = html.xpath('//li[last()-1]/a/text()')
 print(result)
9)注意事項

??A)使用xpath語法吕嘀,應該使用Element.xpath方法來執(zhí)行xpath的選擇,xpath函數返回的永遠都是一個列表贞瞒;
??B)獲取文本偶房,是通過xpath中的text()函數;
??C)在某個標簽下军浆,再執(zhí)行xpath函數棕洋,獲取這個標簽下的子孫元素時,應該在//之前加一個點.乒融,代表是在在當前元素下獲取掰盘。

4摄悯、實戰(zhàn)示例

??使用requestsxpath爬取“電影天堂”中“最新電影”的所有數據,示例代碼如下:

import requests
from lxml import etree

def url_status_code(target_url, headers):
    '''
    獲取target_url的status_code(即url訪問狀態(tài)碼)愧捕,常用狀態(tài)碼如下:
    200:返回消息為“OK”奢驯,描述-請求被確認
    201:返回消息為“Created”,描述-請求是完整的次绘,新的資源被創(chuàng)建
    202:返回消息為“Accepted”叨橱,描述-請求被接受,但未處理完
    301:返回消息為“Moved Permanently”断盛,描述-被請求的頁面已經移動到了新的URL下
    302:返回消息為“Found”罗洗,描述-被請求的頁面暫時性地移動到了新的URL下
    303:返回消息為“See Other”,描述-被請求的頁面可以在一個不同的URL下找到
    400:返回消息為“Bad Request”钢猛,描述-服務器無法識別請求
    403:返回消息為“Forbidden”伙菜,描述-禁止訪問所請求的頁面
    500:返回消息為“Internal Server Error”,描述-請求不完整命迈,服務器遇見了出乎意料的狀況
    502:返回消息為“Bad Gateway”贩绕,描述-請求不完整,服務器從上游服務器接受了一個無效的響應
    503:返回消息為“Service Unavailable”壶愤,描述-請求不完整淑倾,服務器暫時重啟或關閉
    504:返回消息為“Gateway Timeout”,描述-網關超時
    :param target_url: (類型:字符串)指定的url地址
    :param headers: (類型:字典)設置的請求頭信息
    :return: (類型:整型數值)返回指定url的請求狀態(tài)碼
    '''
    response = requests.get(target_url, headers=headers)    # 使用requests的get()方法請求target_url
    status_code = response.status_code  # 獲取url訪問的請求狀態(tài)碼征椒,并賦值給status_code
    return status_code  # 返回指定url的請求狀態(tài)碼

def get_page_url(base_domain, page_num_max, headers):
    '''
    獲取指定頁碼數中每頁的url地址娇哆,該函數為生成器。
    :param base_domain: (類型:字符串)訪問站點的域名
    :param page_num_max: (類型:整型數值)最大獲取頁碼數
    :param headers: (類型:字典)設置的請求頭信息
    :return: (類型:字符串)返回每頁的url地址
    '''
    page_num = 1
    while page_num <= page_num_max:
        page_url = base_domain + 'html/gndy/dyzz/list_23_{}.html'.format(page_num)  # 拼接每頁的url地址
        status_code = url_status_code(page_url, headers)    # 獲取指定url頁面的請求狀態(tài)碼
        '''
        判斷每個url頁面的請求狀態(tài)碼是否為200勃救,如果是碍讨,則返回,否則結束蒙秒。
        '''
        if status_code == 200:
            page_num += 1
            yield page_url
        else:
            break

def crawl_html(target_url, headers):
    '''
    獲取target_url的內容勃黍,并返回
    :param target_url: (類型:字符串)指定的url地址
    :param headers: (類型:字典)設置的請求頭信息
    :return: 返回target_url頁面內容
    '''
    response = requests.get(target_url, headers = headers)
    text = response.content.decode('gbk', errors = 'ignore')
    return text

def get_detail_url(text, base_domain):
    '''
    獲取指定text中每個對象的詳情頁url,該函數為生成器晕讲。
    :param text: 指定的text內容
    :param base_domain: (類型:字符串)訪問站點的域名
    :return: (類型:字符串)返回詳情頁url
    '''
    html = etree.HTML(text)
    tables = html.xpath("http://table[@class='tbspan']")
    for table in tables:
        detail_url = base_domain + table.xpath(".//a/@href")[0]
        yield detail_url

def parse_detail_url(detail_url, headers):
    '''
    解析詳情頁中的內容
    :param detail_url: (類型:字符串)指定的詳情頁url
    :param headers: (類型:字典)設置的請求頭信息
    :return: (類型:字典)返回詳情頁中解析出來的內容
    '''
    movie = {}
    text = crawl_html(detail_url, headers)
    html = etree.HTML(text)
    title = html.xpath("http://div[@class='title_all']//font[@color='#07519a']//text()")[0]
    movie['title'] = title
    zoom = html.xpath("http://div[@id='Zoom']")[0]
    cover = zoom.xpath(".//img/@src")
    if cover != []:
        cover = cover[0]
    movie['cover'] = cover
    infos = zoom.xpath(".//text()")

    def parse_info(info, rule):
        return info.replace(rule, "").strip()

    for index, info in enumerate(infos):
        if info.startswith("◎譯  名"):
            info = parse_info(info, "◎譯  名")
            movie['translation'] = info
        elif info.startswith("◎片  名"):
            info = parse_info(info, "◎片  名")
            movie['name'] = info
        elif info.startswith("◎年  代"):
            info = parse_info(info, "◎年  代")
            movie['year'] = info
        elif info.startswith("◎產  地"):
            info = parse_info(info, "◎產  地")
            movie['country'] = info
        elif info.startswith("類  別"):
            info = parse_info(info, "類  別")
            movie['category'] = info
        elif info.startswith("語  言"):
            info = parse_info(info, "語  言")
            movie['language'] = info
        elif info.startswith("字  幕"):
            info = parse_info(info, "字  幕")
            movie['subtitles'] = info
        elif info.startswith("◎上映日期"):
            info = parse_info(info, "◎上映日期")
            movie['release_date'] = info
        elif info.startswith("◎IMDb評分"):
            info = parse_info(info, "◎IMDb評分")
            movie['IMDb'] = info
        elif info.startswith("◎豆瓣評分"):
            info = parse_info(info, "◎豆瓣評分")
            movie['douban_rating'] = info
        elif info.startswith("◎片  長"):
            info = parse_info(info, "◎片  長")
            movie['duration'] = info
        elif info.startswith("◎導  演"):
            info = parse_info(info, "◎導  演")
            movie['director'] = info
        elif info.startswith("◎編  劇"):
            info = parse_info(info, "◎編  劇")
            writers = [info]
            for x in range(index+1, len(infos)):
                writer = infos[x].strip()
                if writer.startswith("◎"):
                    break
                writers.append(writer)
            movie['writers'] = writers
        elif info.startswith("◎主  演"):
            info = parse_info(info, "◎主  演")
            actors = [info]
            for x in range(index + 1, len(infos)):
                actor = infos[x].strip()
                if actor.startswith("◎"):
                    break
                actors.append(actor)
            movie['actors'] = actors
        elif info.startswith("◎標  簽"):
            info = parse_info(info, "◎標  簽")
            movie['label'] = info
        elif info.startswith("◎簡  介"):
            info = parse_info(info, "◎簡  介")
            for x in range(index + 1, len(infos)):
                profile = infos[x].strip()
                if profile.startswith("【下載地址】"):
                    break
                movie['profile'] = profile
    download_url = html.xpath("http://td[@bgcolor='#fdfddf']/a/text()")
    movie['download_url'] = download_url
    return movie

if __name__ == '__main__':
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.9 Safari/537.36',
        'Cookie': '37cs_pidx=1; 37cs_user=37cs20498016905; 37cs_show=253; UM_distinctid=171521ba5e1a5-0d67214e920904-721f3a40-13c680-171521ba5e27a; bz_finger=6133b30a7ebc8a1a448492bb4dbea710; CNZZDATA1260535040=1919690611-1586220307-https%253A%252F%252Fwww.dytt8.net%252F%7C1586225707; cscpvrich5041_fidx=4',
    }
    base_domain = 'https://www.dytt8.net/'
    page_num_max = 1  # 可以通過修改page_num_max的值來確定需要爬取總頁數
    movies = []
    page_url_generator = get_page_url(base_domain, page_num_max, headers)
    for page_url in page_url_generator:
        text = crawl_html(page_url, headers)
        detail_url_generator = get_detail_url(text, base_domain)
        for detail_url in detail_url_generator:
            movie = parse_detail_url(detail_url, headers)
            movies.append(movies)
            print(movie)
    print(movies)
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末覆获,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子瓢省,更是在濱河造成了極大的恐慌弄息,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件净捅,死亡現(xiàn)場離奇詭異疑枯,居然都是意外死亡辩块,警方通過查閱死者的電腦和手機蛔六,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進店門荆永,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人国章,你說我怎么就攤上這事具钥。” “怎么了液兽?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵骂删,是天一觀的道長。 經常有香客問我四啰,道長宁玫,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任柑晒,我火速辦了婚禮欧瘪,結果婚禮上,老公的妹妹穿的比我還像新娘匙赞。我一直安慰自己佛掖,他們只是感情好,可當我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布涌庭。 她就那樣靜靜地躺著芥被,像睡著了一般。 火紅的嫁衣襯著肌膚如雪坐榆。 梳的紋絲不亂的頭發(fā)上拴魄,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天,我揣著相機與錄音席镀,去河邊找鬼羹铅。 笑死,一個胖子當著我的面吹牛愉昆,可吹牛的內容都是我干的职员。 我是一名探鬼主播,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼跛溉,長吁一口氣:“原來是場噩夢啊……” “哼焊切!你這毒婦竟也來了?” 一聲冷哼從身側響起芳室,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤专肪,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后堪侯,有當地人在樹林里發(fā)現(xiàn)了一具尸體嚎尤,經...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年伍宦,在試婚紗的時候發(fā)現(xiàn)自己被綠了芽死。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片乏梁。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖关贵,靈堂內的尸體忽然破棺而出遇骑,到底是詐尸還是另有隱情,我是刑警寧澤揖曾,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布落萎,位于F島的核電站,受9級特大地震影響炭剪,放射性物質發(fā)生泄漏练链。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一奴拦、第九天 我趴在偏房一處隱蔽的房頂上張望兑宇。 院中可真熱鬧,春花似錦粱坤、人聲如沸隶糕。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽枚驻。三九已至,卻和暖如春株旷,著一層夾襖步出監(jiān)牢的瞬間再登,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工晾剖, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留锉矢,地道東北人。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓齿尽,卻偏偏與公主長得像沽损,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子循头,可洞房花燭夜當晚...
    茶點故事閱讀 44,713評論 2 354

推薦閱讀更多精彩內容