Ch5 Lxml庫與Xpath語法

概要


Lxml庫是基于libxml2XML解析庫Python封裝起宽。該模塊使用C語言編寫陪拘,解析速度比BeautifulSoup更快磕仅。Lxml庫使用Xpath語法解析定位網(wǎng)頁數(shù)據(jù)荐吉。

將講解Lxml庫在MacLinux環(huán)境中的安裝方法,還將介紹Lxml庫的使用方法及Xpath的語法知識炬藤,而且通過案例對正則表達式BeautifulSoupLxml進行性能對比碴里。

主要涉及的知識點:

  • Lxml庫:學會各個系統(tǒng)下Lxml庫的安裝和使用方法沈矿。

  • Xpath語法:學會Xpath語法并通過Xpath語法提取所需的網(wǎng)頁信息。

  • 性能對比:通過案例對正則表達式咬腋、BeautifulSoupLxml進行性能對比羹膳。

  • Requests和Lxml庫組合應(yīng)用:演示如何利用這兩大庫進行爬蟲的方法和技巧。


1. Lxml庫的安裝與使用方法

Lxml庫解析網(wǎng)頁數(shù)據(jù)快根竿,但安裝過程卻相對困難陵像。主要講解Lxml庫在MacLinux環(huán)境中的安裝方法及Lxml庫的簡單用法。

1.1 Lxml庫的安裝(Mac寇壳、Linux)

這里我們主要講下Linux系統(tǒng)下的安裝

  • Linux系統(tǒng)

Linux系統(tǒng)安裝Lxml庫最簡單醒颖,在終端輸入:

apt-get install Python3-lxml

這樣就完后才能了Linux系統(tǒng)下Lxml庫的安裝。

1.2 Lxml庫的使用

  • 修正HTML代碼

Lxml為XML解析庫壳炎,但也很好的支持了HTML文檔的解析功能泞歉,這為使用Lxml庫爬取網(wǎng)絡(luò)信息提供了支持條件。

這樣就可以通過Lxml庫來解析HTML文檔了:

from lxml import etree

text = '''
<div>
 <ul>
  <li class ="red"><h1>red flowers</h1></li>
  <li class ="yellow"><h2>yellow flowers</h2></li>
  <li class ="white"><h3>white flowers</h3></li>
  <li class ="black"><h4>black flowers</h4></li>
  <li class ="blue"><h5>blue flowers</h5></li>
 </ul>
</div>
'''

html = etree.HTML(text)
print(html)
# Lxml庫解析數(shù)據(jù)匿辩,為Element對象

打印結(jié)果如下:

首先導入Lxml中的etree庫腰耙,然后利用etree.HTML進行初始化,最后把結(jié)果打印出來铲球⊥ε樱可以看出,etree庫把HTML文檔解析為Element對象睬辐,可以通過以下代碼輸出解析過的HTML文檔挠阁。

from lxml import etree

text = '''
<div>
 <ul>
  <li class ="red"><h1>red flowers</h1></li>
  <li class ="yellow"><h2>yellow flowers</h2></li>
  <li class ="white"><h3>white flowers</h3></li>
  <li class ="black"><h4>black flowers</h4></li>
  <li class ="blue"><h5>blue flowers</h5>
 </ul>
</div>
'''

html = etree.HTML(text)
result = etree.tostring(html)
print(result)
# Lxml庫解析可自動修正HTML

打印結(jié)果如下:

Lxml解析后的文檔

這里體現(xiàn)了Lxml庫一個非常使用的功能就是自動修正HTML代碼,應(yīng)該注意到了最后一個li標簽溯饵,其實是把尾標簽刪掉了侵俗,是不閉合的。不過Lxml因為集成了libxml2的特性丰刊,具有自動修正HTML代碼的功能隘谣,這里不僅補齊了li標簽,而且還添加了htmlbody標簽。

  • 讀取HTML文件

除了直接讀取字符串寻歧,Lxml庫還支持從文件中提取內(nèi)容掌栅。我們可以通過Pycharm新建一個flower.html文件。在所需建立文件的位置右擊码泛,在彈出的快捷菜單中選擇New|HTML File命令猾封,如下圖:

新建好的HTML文件,已經(jīng)自動生成了html噪珊、head和body標簽晌缘,也可以通過單擊Pycharm右上角的瀏覽器符號,在本地打開制作好的HTML文件痢站,如下圖:

把前面的字符串復制在HTML文檔中磷箕,如下圖,最后通過瀏覽器打開制作好的HTML文件阵难。

這樣便可通過Lxml庫讀取HTML文件中的內(nèi)容了岳枷,可以通過下面的代碼讀取:

from lxml import etree

html = etree.parse('flower.html')
result = etree.tostring(html, pretty_print=True)
print(result)

注:<meta charset="UTF-8"/>中的/必須要帶呜叫,不然會解析報錯空繁。

  • 解析HTML文件

完成了前面的步驟后,便可利用requests庫來獲取HTML文件怀偷,用Lxml庫來解析HTML文件了家厌。

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/70.0.3538.67 Safari/537.36'
}

res = requests.get('https://book.douban.com/top250', headers=headers)
html = etree.HTML(res.text)
result = etree.tostring(html)
print(result)

2. Xpath 語法

Xpath是一門在XML文檔中查找信息的語言,對HTML文檔也有很好的支持椎工。將介紹Xpath的常用語法饭于,Xpath語言在爬蟲中的使用技巧。最后通過案例對正則表達式维蒙、BeautifulSoup和Lxml進行性能對比掰吕。

2.1 節(jié)點關(guān)系

  • 父節(jié)點

每個元素及屬性都有一個父節(jié)點,在下面的例子中颅痊,user元素是name殖熟、sexidgoal元素的父節(jié)點斑响。

<user>
    <name>xiao ming</name>
    <sex>man</sex>
    <id>34</id>
    <goal>89</goal>
</user>
  • 子節(jié)點
    元素節(jié)點可有0個菱属、一個或多個子節(jié)點,在下面的例子中舰罚,name纽门、sexidgoal元素都是user元素的子節(jié)點营罢。
<user>
    <name>xiao ming</name>
    <sex>man</sex>
    <id>34</id>
    <goal>89</goal>
</user>
  • 同胞節(jié)點
    同胞節(jié)點擁有相同的父節(jié)點赏陵,在下面的例子中,namesex蝙搔、idgoal元素都是同胞節(jié)點
<user>
    <name>xiao ming</name>
    <sex>man</sex>
    <id>34</id>
    <goal>89</goal>
</user>
  • 先輩節(jié)點
    先輩節(jié)點指某節(jié)點的父缕溉、父的父節(jié)點等,在下面的例子中吃型,name元素的先輩是user元素和user_database元素:
<user_database>
<user>
    <name>xiao ming</name>
    <sex>man</sex>
    <id>34</id>
    <goal>89</goal>
</user>
</user_database>
  • 后代節(jié)點

后代節(jié)點指某個節(jié)點的子節(jié)點证鸥、子節(jié)點的子節(jié)點等,在下面的例子中败玉,user_database的后代是user敌土、name、sex运翼、id及goal元素:

<user_database>
<user>
    <name>xiao ming</name>
    <sex>man</sex>
    <id>34</id>
    <goal>89</goal>
</user>
</user_database>

2.2 節(jié)點選擇

Xpath使用路徑表達式在XML文檔中選取節(jié)點。節(jié)點是通過演著路徑或者step來選取的兴枯,如下表:

表達式 描述
nodename 選取此節(jié)點的所有子節(jié)點
/ 從根節(jié)點選取
// 從匹配選擇的當前節(jié)點選擇文檔中的節(jié)點血淌,而不考慮他們的位置
. 選取當前節(jié)點
.. 選取當前節(jié)點的父節(jié)點
@ 選取屬性

通過前面的例子進行舉例,如下表所示:

表達式 描述
user_database 選取元素user_database的所有子節(jié)點
/user_database 選取根元素user_database财剖。注釋:假如路徑起始于正斜杠(/)悠夯,則此路徑始終代表到某元素的絕對路徑
user_database/user 選取屬于user_database的子元素的所有user元素
//user 選取所有user子元素,而不管它們在文檔中的位置
user_database//user 選取屬于user_database元素的后代的所有user元素躺坟,而不管它們位于user_database之下的什么位置
//@attribute 選取名為attribute的所有屬性

Xpath語法中的謂語用來查找某個特定的節(jié)點或者包含某個指定值的節(jié)點沦补,謂語被嵌在方括號中。常見的謂語如下表:

路徑表達式 結(jié)果
/user_database/user[1] 選取屬于user_database子元素的第一個user元素
//li[@attribute] 選取所有擁有名為attribute屬性的li元素
//li[@attribute='red'] 選取所有l(wèi)i元素咪橙,且這些元素擁有值為red的attribute屬性

Xpath中也可以使用通配符來選取位置的元素夕膀,常用的就是“*”通配符,它可以匹配任何元素節(jié)點美侦。

2.3 使用技巧

在爬蟲實戰(zhàn)中产舞,Xpath路徑可以通過Chrome復制得到,如下圖:

復制Xpath

(1)鼠標光標定位到想要提取的數(shù)據(jù)位置菠剩,右擊易猫,從彈出的快捷菜單中選擇“檢查”命令。

(2)在網(wǎng)頁源代碼中右擊所選元素具壮。

(3)從彈出的快捷菜單中選擇Copy Xpath命令准颓,這時便能得到。

//*[@id="qiushi_tag_121174862"]/div[1]/a[2]/h2

通過代碼即可得到用戶id:

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/70.0.3538.67 Safari/537.36'
}

url = 'http://www.qiushibaike.com/text/'

res = requests.get(url, headers=headers)

selector = etree.HTML(res.text)

print(selector)
id = selector.xpath('//*[@id="qiushi_tag_121166084"]/div[1]/a[2]/h2/text()')

print(id)

注意:通過/text()可以獲取標簽中的文字信息棺妓。

結(jié)果為

['\n誰搶了我微信昵稱\n']

上面的結(jié)果為列表的數(shù)據(jù)結(jié)構(gòu)攘已,可以通過切片獲取為字符串數(shù)據(jù)結(jié)構(gòu):

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/70.0.3538.67 Safari/537.36'
}

url = 'http://www.qiushibaike.com/text/'

res = requests.get(url, headers=headers)

selector = etree.HTML(res.text)

id = selector.xpath('//*[@id="qiushi_tag_121166084"]/div[1]/a[2]/h2/text()')[0]

print(id)

當需要進行用戶ID的批量爬取時,通過類似于BeautifulSoup中的selector()方法刪除謂語部分是不可行的涧郊。這時的思路為“先抓大后抓小贯被,尋找循環(huán)點”。打開Chrome瀏覽器進行“檢查”,通過“三角形符號”折疊元素彤灶,找到每個段子完整的信息標簽看幼,如下圖所示,每一個div標簽為一個段子信息幌陕。

尋找循環(huán)點

(1)首先通過復制構(gòu)造div標簽路徑诵姜,此時的路徑為:

  

//*[@class="article block untagged mb15" ]

這樣就定位到了每個段子信息,這就是循環(huán)點搏熄。

(2)通過Chrome瀏覽器進行“檢查”定位用戶ID棚唆,復制Xpath到記事本中。

//*[@id="qiushi_tag_121174765"]/div[1]/a[2]/h2

因為第一部分為循環(huán)部分心例,將其刪除得到:

div[1]/a[2]/h2

這便是用戶ID的信息宵凌。

注意:這里就不需要斜線作為開頭了。

同時也可以這樣寫:

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/70.0.3538.67 Safari/537.36'
}

url = 'https://www.qiushibaike.com/text/'

res = requests.get(url, headers=headers)

# print(res.text)

# 標簽為<div class="article block untagged mb15 typs_hot" id="qiushi_tag_121132915">

selector = etree.HTML(res.text)

url_infos = selector.xpath('//div[@class="article block untagged mb15 typs_hot"]/div[1]/a[2]/h2/text()')

print(url_infos)

更可以:

url_infos = selector.xpath('//div/div[1]/a[2]/h2/text()')

print(url_infos)

有時候會遇到相同的字符開頭的多個標簽:

<li class="tag-1">需要的內(nèi)容1</li>
<li class="tag-2">需要的內(nèi)容2</li>
<li class="tag-3">需要的內(nèi)容3</li>

想同時爬取時止后,不需要構(gòu)造多個Xpath路徑瞎惫,通過starts-with()便可以獲取多個標簽內(nèi)容。

from lxml import etree

html1 = '''
<li class="tag-1">需要的內(nèi)容1</li>
<li class="tag-2">需要的內(nèi)容2</li>
<li class="tag-3">需要的內(nèi)容3</li>
'''

selector = etree.HTML(html1)

contents = selector.xpath('//li[starts-with(@class,"tag")]/text() ')

print(contents)

for content in contents:
    print(content)
    # starts-with可獲取類似標簽的信息

上面的示例也可以用starts-with方法來實現(xiàn):

import requests
from lxml import etree

url = 'https://www.qiushibaike.com/text/'

headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) '
                  'AppleWebKit/537.36 (KHTML, like Gecko) '
                  'Chrome/70.0.3538.67 Safari/537.36'
}

response = requests.get(url, headers=headers)
# print(response.text)

selector = etree.HTML(response.text)
# print(selector)

# 循環(huán)點: <div class="article block untagged mb15 typs_long" id="qiushi_tag_121169478">
# //*[@id="qiushi_tag_121169478"]/div[1]/a[2]/h2

result = selector.xpath('//div[starts-with(@class,"article block untagged mb15")]/div[1]/a[2]/h2/text()')
print(result)

當遇到標簽套標簽情況時:

<div class="red">需要的內(nèi)容1
    <h1>需要的內(nèi)容2</h1>
</div>>

想同時獲取文本內(nèi)容译株,可以通過string(.)完成:

from lxml import etree

html2 = '''
<div class="red">需要的內(nèi)容1
    <h1>需要的內(nèi)容2</h1>
</div>>
'''

selector = etree.HTML(html2)

content1 = selector.xpath('//div[@class="red"]')[0]

content2 = content1.xpath('string(.)')
print(content2)
# string(.)方法可用于標簽套標簽情況
string(.)用法

2.4 性能對比

前面提到Lxml庫的解析速度快瓜喇,但是口說無憑,將會通過代碼對正則表達式歉糜、BeautifulSoup乘寒、Lxml進行性能對比。

(1)通過3種方法爬取糗事百科文字內(nèi)容中的信息匪补,如下圖:

(2)由于是比較性能伞辛,爬取的信息并不是很多,爬取的信息有:用戶ID叉袍、發(fā)表段子文字信息始锚、好笑數(shù)量和評論數(shù)量,如下圖:

(3)爬取的數(shù)據(jù)只做返回喳逛,不存儲瞧捌。

代碼如下:

import requests
from lxml import etree
from bs4 import BeautifulSoup
import re
import time

urls = ['https://www.qiushibaike.com/text/page/{}'.format(str(i)) for i in range(1, 14)]

headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) '
                  'AppleWebKit/537.36 (KHTML, like Gecko) '
                  'Chrome/70.0.3538.67 Safari/537.36'
}


def re_scraper(url):
    # 用正則表達式
    res = requests.get(url, headers=headers)
    ids = re.findall('<h2>(.*?)</h2>', res.text, re.S)
    # print(len(ids))
    contents = re.findall('<div class="content">.*?<span>(.*?)</span>', res.text, re.S)
    # print(len(contents))
    laughs = re.findall('<span class="stats-vote">.*?<i class="number">(\d+)</i>', res.text, re.S)
    # print(len(laughs))
    # print(laughs)
    # <i class="number">47</i> 評論
    comments = re.findall('<i class="number">(\d+)</i> 評論', res.text, re.S)
    # print(len(comments))
    # print(comments)
    for id, content, laugh, comment in zip(ids, contents, laughs, comments):
        info = {
            'id': id.strip(),
            'content': content.strip(),
            'laugh': laugh,
            'comment': comment
        }
        return info


def bs_scraper(url):
    # BeautifulSoup爬蟲
    res = requests.get(url, headers=headers)
    soup = BeautifulSoup(res.text, 'lxml')

    # #qiushi_tag_121175295 > div.author.clearfix > a:nth-child(2) > h2
    ids = soup.select('div.author.clearfix > a > h2')
    # print(ids)
    # print(len(ids))

    # #qiushi_tag_121190672 > a > div > span
    # 直接用a>div>span 檢索出來的結(jié)果會有56條,應(yīng)該縮小檢索范圍
    contents = soup.select(' a.contentHerf > div > span')
    # print(contents)
    # print(len(contents))

    # #qiushi_tag_121190672 > div.stats > span.stats-vote > i
    laughs = soup.select('div.stats > span.stats-vote > i')
    # print(laughs)
    # print(len(laughs))

    # #c-121182508 > i
    comments = soup.select('span > a.qiushi_comments > i')
    # print(comments)
    # print(len(comments))

    for id, content, laugh, comment in zip(ids, contents, laughs, comments):
        info = {
            'id': id.get_text(),
            'content': content.getText(),
            'laugh': laugh.getText(),
            'comment': comment.get_text()
        }
        return info


def lxml_scraper(url):
    # lxml爬蟲
    res = requests.get(url, headers=headers)
    selector = etree.HTML(res.text)
    # <div class="article block untagged mb15 typs_long" id="qiushi_tag_121178203">
    # //*[@id="qiushi_tag_121178203"]

    url_infos = selector.xpath('//div[starts-with(@id,"qiushi_tag_")]')
    # print(url_infos)
    # print(len(url_infos))
    try:
        for url_info in url_infos:
            # //*[@id="qiushi_tag_121178203"]/div[1]/a[2]/h2
            # //*[@id="qiushi_tag_121192750"]/div[1]/span[2]/h2
            id = url_info.xpath('div[1]/a[2]/h2/text()')
            # print(id)

            # //*[@id="qiushi_tag_121191682"]/a[1]/div/span
            content = url_info.xpath('a[1]/div/span/text()')
            # print(content)

            # //*[@id="qiushi_tag_115909114"]/div[2]/span[1]/i
            laugh = url_info.xpath('div[2]/span[1]/i/text()')
            # print(laugh)

            # //*[@id="c-121164484"]/i
            # //*[@id="c-121164484"]
            # //*[@id="qiushi_tag_121164484"]/div[2]/span[2]
            comment = url_info.xpath('div[2]/span[2]/a[1]/i/text()')
            # print(comment)

            info = {
                'id': id,
                'content': content,
                'laugh': laugh,
                'comment': comment
            }
            return info
    except IndexError:
        print("error")


# print(re_scraper(urls[0]))
# print(bs_scraper(urls[0]))
# print((lxml_scraper(urls[0])))

if __name__ == '__main__':
    # 程序主入口
    for name, scraper in [('Regular 67 expressions', re_scraper), ('BeautifulSoup', bs_scraper),
                          ('Lxml', lxml_scraper)]:
        start = time.time()
        for url in urls:
            scraper(url)
        end = time.time()
        print(name, end - start)
性能對比

由于硬件條件的不同润文,執(zhí)行的結(jié)果會存在一定的差異性姐呐。下表總結(jié)了各種爬蟲方法的優(yōu)缺點:

爬取方法 性能 使用難度 安裝難度
正則表達式 困難 簡單(內(nèi)置模塊)
BeautifulSoup 剪短 簡單
Lxml 簡單 相對困難

當網(wǎng)頁結(jié)構(gòu)簡單并且想要避免額外依賴的話(不需要安裝庫),使用正則表達式更為合適典蝌。當需要爬取的數(shù)據(jù)量較少時曙砂,使用較慢的BeautifulSoup也不成問題。當數(shù)據(jù)量大骏掀,需要追求效益時鸠澈,Lxml是最好的選擇柱告。


3. 綜合案例1----爬取豆瓣網(wǎng)圖書TOP250的數(shù)據(jù)

將利用Requests和Lxml第三方庫,爬取豆瓣網(wǎng)圖書TOP250的數(shù)據(jù)笑陈,并存儲到CSV格式的文件中际度。

3.1 將數(shù)據(jù)存儲到CSV文件中

前面爬取的數(shù)據(jù)要么打印到屏幕上,要么存儲到TXT文檔中涵妥,這些格式并不利于數(shù)據(jù)的存儲乖菱。那么大家平時是用什么來存儲數(shù)據(jù)的呢?大部分讀者可能是使用微軟公司的Excel來儲存數(shù)據(jù)的蓬网,大規(guī)模的數(shù)據(jù)則是使用數(shù)據(jù)庫窒所。CSV是存儲表格數(shù)據(jù)的常用文件格式,Excel和很多應(yīng)用都支持CSV格式帆锋,因為它很簡潔吵取。下面就是一個CSV文件的例子:

id,name
1,xiaoming
2,zhangsan
3,peter

Python中的csv庫可以創(chuàng)建CSV文件,并寫入數(shù)據(jù):

import csv

# 創(chuàng)建CSV文件
fp = open('C:/Users/Think/Desktop/test.csv', 'w+')
writer = csv.writer(fp)
writer.writerow(('id', 'name'))
writer.writerow(('1', 'xiaoming'))
writer.writerow(('2', 'zhangsan'))
writer.writerow(('3', 'peter'))
# 寫入行

這時的本機桌面上會生成名為test的CSV文件窟坐,用記事本打開海渊,效果如下:

3.2 爬蟲思路分析

(1) 爬取的內(nèi)容為豆瓣網(wǎng)圖書TOP250的信息。

(2)爬取豆瓣網(wǎng)圖書TOP250的10頁信息哲鸳,通過手動瀏覽,以下為前4頁的網(wǎng)址:

https://book.douban.com/top250?start=0
https://book.douban.com/top250?start=25
https://book.douban.com/top250?start=50
https://book.douban.com/top250?start=75

(3)需要爬取的信息有:書名盔憨、書本的URL鏈接徙菠、作者、出版社和出版時間郁岩,書本價格婿奔、評分和評價,如下圖:

注意:這里只爬了第一作者

(4)運用Python中的csv庫问慎,把爬取的信息存儲在本地的CSV文本中萍摊。

3.3 爬蟲代碼及分析

from lxml import etree
import requests
import csv

fp = open('C:/Users/Think/Desktop/doubanbook.csv', 'wt', newline='', encoding='utf-8')

writer = csv.writer(fp)
# 寫入header
writer.writerow(('name', 'url', 'author', 'publisher', 'date', 'price', 'rate', 'comment'))

urls = ['https://book.douban.com/top250?start={}'.format(str(i * 25)) for i in range(0, 10)]

headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) '
                  'AppleWebKit/537.36 (KHTML, like Gecko) '
                  'Chrome/70.0.3538.67 Safari/537.36'
}

for url in urls:
    # print(url)
    html = requests.get(url, headers=headers)
    selector = etree.HTML(html.text)
    # //*[@id="content"]/div/div[1]/div/table[1]/tbody/tr
    infos = selector.xpath('//tr[@class="item"]')
    # print(len(infos))
    # name ,  url  ,  author ,  publisher ,  date ,  price ,  rate ,  comment
    for info in infos:
        # //*[@id="content"]/div/div[1]/div/table[1]/tbody/tr/td[2]/div[1]/a
        # td[2]/div[1]/a
        name = info.xpath('td[2]/div[1]/a/text()')[0]
        # print(name.strip())

        # //*[@id="content"]/div/div[1]/div/table[1]/tbody/tr/td[2]/div[1]/a
        url = info.xpath('td[2]/div[1]/a/@href')[0]
        # print(url)

        # //*[@id="content"]/div/div[1]/div/table[1]/tbody/tr/td[2]/p[1]
        book_infos = info.xpath('td[2]/p[1]/text()')[0].split('/')
        # print(book_infos)
        author = book_infos[0]
        # print(author)

        publisher = book_infos[-3]
        date = book_infos[-2]
        price = book_infos[-1]

        # //*[@id="content"]/div/div[1]/div/table[1]/tbody/tr/td[2]/div[2]/span[2]
        rate = info.xpath('td[2]/div[2]/span[2]/text()')[0]
        # print(rate)

        # //*[@id="content"]/div/div[1]/div/table[1]/tbody/tr/td[2]/p[2]/span
        comments = info.xpath('td[2]/p[2]/span/text()')
        # print(comments)
        comment = comments[0] if len(comments) != 0 else '空'

        writer.writerow((name, url, author, publisher, date, price, rate, comment))

fp.close()

程序運行的結(jié)果保存在計算機里文件名為doubanbook的csv文件中,如通過excel打開會出現(xiàn)亂碼錯誤如叼,如圖:

亂碼錯誤

可以通過記事本打開冰木,將其另存為編碼為UTF-8的文件,便不會出現(xiàn)亂碼問題笼恰。

這時再通過Excel打開文件踊沸,便不會出現(xiàn)亂碼問題了。

解決結(jié)果

4. 綜合案例2----爬取起點中文網(wǎng)小說信息

將利用Requests 和 Lxml第三方庫社证,爬取起點中文網(wǎng)小說信息逼龟,并存儲到Excel文件中。

4.1 將數(shù)據(jù)存儲到Excel文件中

使用Python的第三方庫xlwt追葡,可將數(shù)據(jù)寫入Excel中腺律,通過PIP進行安裝即可奕短。

通過下面的代碼,便可將數(shù)據(jù)寫入Excel中:

import xlwt

# 將數(shù)據(jù)寫入Excel的庫文件中

# 創(chuàng)建工作簿
book = xlwt.Workbook(encoding='utf-8')
# 創(chuàng)建工作表
sheet = book.add_sheet('Sheet1')

# 在相應(yīng)單元格寫入數(shù)據(jù)
sheet.write(0, 0, 'python')
sheet.write(1, 1, 'love')
sheet.write(1, 3, 'ozan')
sheet.write(3, 1, 'wen')
# 保存到文件中
book.save('test.xls')

程序運行后匀钧,可在本地找到該Excel文件翎碑,結(jié)果如圖:

代碼說明一下:

(1)導入xlwt庫
(2)通過Workbook()方法創(chuàng)建一個工作簿
(3)創(chuàng)建一個名字為Sheet1的工作表
(4)寫入數(shù)據(jù),可以看出第一個和第二個參數(shù)為Excel表格的單元格的位置榴捡,第三個為寫入內(nèi)容杈女。
(5)保存到文件

4.2 爬蟲思路分析

(1)爬取的內(nèi)容為起點中文網(wǎng)的全部作品信息(https://www.qidian.com/),如下圖

(2)爬取起點中文網(wǎng)的全部作品信息的前100頁吊圾,通過手動瀏覽达椰,下面為第2頁的網(wǎng)址

https://www.qidian.com/all?orderId=&style=1&pageSize=20&siteid=1&pubflag=0&hiddenField=0&page=2
https://www.qidian.com/all?orderId=&style=1&pageSize=20&siteid=1&pubflag=0&hiddenField=0&page=3

猜想這些字段是用來控制作品分類的,我們爬取的為全部作品项乒,依次刪掉一些參數(shù)檢查啰劲,發(fā)現(xiàn)將網(wǎng)址改為https://www.qidian.com/all?page=2后,也可以訪問相同的信息檀何,通過多頁檢驗蝇裤,證明了修改的合理性,以此來構(gòu)造前100頁URL频鉴。

(3)需要爬取的信息有:小說名栓辜、作者ID、小說類型垛孔、完成情況藕甩、摘要和字數(shù)。如圖:

(4)運用xlwt庫周荐,把爬取的信息存儲在本地的Excel表格中狭莱。

# 起點中文網(wǎng)

import xlwt
import requests
from lxml import etree
import time
import re
from fontTools.ttLib import TTFont

# 初始化列表,存入爬蟲數(shù)據(jù)
all_info_list = []

dict_en = {
    'one': '1',
    'two': '2',
    'three': '3',
    'four': '4',
    'five': '5',
    'six': '6',
    'seven': '7',
    'eight': '8',
    'nine': '9',
    'period': '.',
    'zero': '0',
}


def get_info(url):
    # 定義獲取爬蟲信息的函數(shù)
    htmltext = requests.get(url)
    # print(html.text)
    selector = etree.HTML(htmltext.text)

    # 獲取字體文件
    # /html/body/div[2]/div[5]/div[2]/div[2]/div/ul/li[1]/div[2]/p[3]/span/style
    # 獲取font-face的第一段信息
    font_html = selector.xpath('/html/body/div[2]/div[5]/div[2]/div[2]/div/ul/li[1]/div[2]/p[3]/span/style')[0]

    # print(etree.tostring(font_html[0]))
    font_face = etree.tostring(font_html)
    # print(str(font_face))
    font_file_url = re.findall('url\((.*?)\)', str(font_face), re.S)[-1].replace('\'', '')
    print(font_file_url)

    b = requests.get(font_file_url)

    with open('new.ttf', 'wb') as f:
        f.write(b.content)
        f.close()
    font_new = TTFont('new.ttf')
    # font_new.saveXML('font_new.xml')
    cmap = font_new.getBestCmap()
    print(cmap)

    # /html/body/div[2]/div[5]/div[2]/div[2]/div/ul/li[1]
    # 定位大標簽概作,以此循環(huán)
    infos = selector.xpath('body/div[2]/div[5]/div[2]/div[2]/div/ul/li')
    print(len(infos))

    for info in infos:
        # title,author,style_1,style_2,style,complete,introduce,word

        # /html/body/div[2]/div[5]/div[2]/div[2]/div/ul/li[1]   /div[2]/h4/a
        title = info.xpath('div[2]/h4/a/text()')[0]
        # print(title)

        # /html/body/div[2]/div[5]/div[2]/div[2]/div/ul/li[1] /div[2]/p[1]/a[1]
        author = info.xpath('div[2]/p[1]/a[1]/text()')[0]
        # print(author)

        # /html/body/div[2]/div[5]/div[2]/div[2]/div/ul/li[1] /div[2]/p[1]/a[2]
        style1 = info.xpath('div[2]/p[1]/a[2]/text()')[0]
        # print(style1)

        # /html/body/div[2]/div[5]/div[2]/div[2]/div/ul/li[1] /div[2]/p[1]/a[3]
        style2 = info.xpath('div[2]/p[1]/a[3]/text()')[0]
        # print(style2)

        style = style1 + '-' + style2
        # print(style)

        # /html/body/div[2]/div[5]/div[2]/div[2]/div/ul/li[1] /div[2]/p[1]/span
        complete = info.xpath('div[2]/p[1]/span/text()')[0]
        # print(complete)

        # /html/body/div[2]/div[5]/div[2]/div[2]/div/ul/li[1] /div[2]/p[2]
        introduce = info.xpath('div[2]/p[2]/text()')[0]
        # print(introduce)
        # print(introduce.strip())

        # /html/body/div[2]/div[5]/div[2]/div[2]/div/ul/li[1] /div[2]/p[3]/span/span
        word_all = info.xpath('div[2]/p[3]/span/span')
        # print(word)
        # print(str(etree.tostring(word[0], encoding='gbk'))[2:-1])
        word_temp = str(etree.tostring(word_all[0]))
        # print(word_temp)
        word_final = re.findall('">(.*?);</span>', word_temp, re.S)[0]
        words = word_final.split(';')
        print(words)

        word = ''
        for char in words:
            # print(int(char[2:]))
            # print(get_real_num(cmap, int(char[2:])))
            # print(cmap[int(char[2:])])
            word += get_real_num(cmap, int(char[2:]))

        info_list = [title, author, style, complete, introduce, word]
        all_info_list.append(info_list)
        time.sleep(1)


def get_real_num(dict, code):
    return dict_en[dict[code]]


# get_info('https://www.qidian.com/all?page=1')
# print(all_info_list)

if __name__ == '__main__':
    # 程序主入口
    urls = ['https://www.qidian.com/all?page={}'.format(str(i)) for i in range(1, 3)]
    for url in urls:
        get_info(url)

    header = ['title', 'author', 'style', 'complete', 'introduce', 'word']
    # 定義表頭
    book = xlwt.Workbook(encoding='utf-8')
    # 創(chuàng)建工作簿
    sheet = book.add_sheet('Sheet1')
    # 創(chuàng)建工作表
    for h in range(len(header)):
        # 寫入表頭
        sheet.write(0, h, header[h])
    i = 1
    for list in all_info_list:
        j = 0
        for data in list:
            sheet.write(i, j, data)
            j += 1
        i += 1

    book.save('xiaoshuo.xls')

注:代碼寫于 2018/11/2腋妙,因此關(guān)于字體反爬更新于此時間段。

程序運行后讯榕,將會存入數(shù)據(jù)到Excel表格中骤素,如下圖:

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市瘩扼,隨后出現(xiàn)的幾起案子谆甜,更是在濱河造成了極大的恐慌,老刑警劉巖集绰,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件规辱,死亡現(xiàn)場離奇詭異,居然都是意外死亡栽燕,警方通過查閱死者的電腦和手機罕袋,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進店門改淑,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人浴讯,你說我怎么就攤上這事朵夏。” “怎么了榆纽?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵仰猖,是天一觀的道長。 經(jīng)常有香客問我奈籽,道長饥侵,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任衣屏,我火速辦了婚禮躏升,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘狼忱。我一直安慰自己膨疏,他們只是感情好,可當我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布钻弄。 她就那樣靜靜地躺著佃却,像睡著了一般。 火紅的嫁衣襯著肌膚如雪窘俺。 梳的紋絲不亂的頭發(fā)上双霍,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天,我揣著相機與錄音批销,去河邊找鬼。 笑死染坯,一個胖子當著我的面吹牛均芽,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播单鹿,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼掀宋,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了仲锄?” 一聲冷哼從身側(cè)響起劲妙,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎儒喊,沒想到半個月后镣奋,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡怀愧,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年侨颈,在試婚紗的時候發(fā)現(xiàn)自己被綠了余赢。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡哈垢,死狀恐怖妻柒,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情耘分,我是刑警寧澤举塔,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站求泰,受9級特大地震影響央渣,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜拜秧,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一痹屹、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧枉氮,春花似錦志衍、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至惹悄,卻和暖如春春叫,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背泣港。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工暂殖, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人当纱。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓呛每,卻偏偏與公主長得像,于是被迫代替她去往敵國和親坡氯。 傳聞我的和親對象是個殘疾皇子晨横,可洞房花燭夜當晚...
    茶點故事閱讀 42,786評論 2 345

推薦閱讀更多精彩內(nèi)容