概要
Lxml庫(kù)是基于libxml2的XML解析庫(kù)的Python封裝。該模塊使用C語(yǔ)言編寫(xiě),解析速度比BeautifulSoup更快。Lxml庫(kù)使用Xpath語(yǔ)法解析定位網(wǎng)頁(yè)數(shù)據(jù)。
將講解Lxml庫(kù)在Mac和Linux環(huán)境中的安裝方法,還將介紹Lxml庫(kù)的使用方法及Xpath的語(yǔ)法知識(shí),而且通過(guò)案例對(duì)正則表達(dá)式、BeautifulSoup和Lxml進(jìn)行性能對(duì)比。
主要涉及的知識(shí)點(diǎn):
Lxml庫(kù):學(xué)會(huì)各個(gè)系統(tǒng)下Lxml庫(kù)的安裝和使用方法。
Xpath語(yǔ)法:學(xué)會(huì)Xpath語(yǔ)法并通過(guò)Xpath語(yǔ)法提取所需的網(wǎng)頁(yè)信息。
性能對(duì)比:通過(guò)案例對(duì)
正則表達(dá)式、BeautifulSoup和Lxml進(jìn)行性能對(duì)比。Requests和Lxml庫(kù)組合應(yīng)用:演示如何利用這兩大庫(kù)進(jìn)行爬蟲(chóng)的方法和技巧。
1. Lxml庫(kù)的安裝與使用方法
Lxml庫(kù)解析網(wǎng)頁(yè)數(shù)據(jù)快,但安裝過(guò)程卻相對(duì)困難。主要講解Lxml庫(kù)在Mac和Linux環(huán)境中的安裝方法及Lxml庫(kù)的簡(jiǎn)單用法。
1.1 Lxml庫(kù)的安裝(Mac、Linux)
這里我們主要講下Linux系統(tǒng)下的安裝
- Linux系統(tǒng)
Linux系統(tǒng)安裝Lxml庫(kù)最簡(jiǎn)單,在終端輸入:
apt-get install Python3-lxml
這樣就完后才能了Linux系統(tǒng)下Lxml庫(kù)的安裝。
1.2 Lxml庫(kù)的使用
- 修正HTML代碼
Lxml為XML解析庫(kù),但也很好的支持了HTML文檔的解析功能,這為使用Lxml庫(kù)爬取網(wǎng)絡(luò)信息提供了支持條件。
這樣就可以通過(guò)Lxml庫(kù)來(lái)解析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庫(kù)解析數(shù)據(jù),為Element對(duì)象
打印結(jié)果如下:

首先導(dǎo)入Lxml中的etree庫(kù),然后利用etree.HTML進(jìn)行初始化,最后把結(jié)果打印出來(lái)??梢钥闯觯?code>etree庫(kù)把HTML文檔解析為Element對(duì)象,可以通過(guò)以下代碼輸出解析過(guò)的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庫(kù)解析可自動(dòng)修正HTML
打印結(jié)果如下:

這里體現(xiàn)了Lxml庫(kù)一個(gè)非常使用的功能就是自動(dòng)修正HTML代碼,應(yīng)該注意到了最后一個(gè)li標(biāo)簽,其實(shí)是把尾標(biāo)簽刪掉了,是不閉合的。不過(guò)Lxml因?yàn)榧闪?code>libxml2的特性,具有自動(dòng)修正HTML代碼的功能,這里不僅補(bǔ)齊了li標(biāo)簽,而且還添加了html和body標(biāo)簽。
- 讀取HTML文件
除了直接讀取字符串,Lxml庫(kù)還支持從文件中提取內(nèi)容。我們可以通過(guò)Pycharm新建一個(gè)flower.html文件。在所需建立文件的位置右擊,在彈出的快捷菜單中選擇New|HTML File命令,如下圖:

新建好的HTML文件,已經(jīng)自動(dòng)生成了html、head和body標(biāo)簽,也可以通過(guò)單擊Pycharm右上角的瀏覽器符號(hào),在本地打開(kāi)制作好的HTML文件,如下圖:

把前面的字符串復(fù)制在HTML文檔中,如下圖,最后通過(guò)瀏覽器打開(kāi)制作好的HTML文件。


這樣便可通過(guò)Lxml庫(kù)讀取HTML文件中的內(nèi)容了,可以通過(guò)下面的代碼讀?。?/p>
from lxml import etree
html = etree.parse('flower.html')
result = etree.tostring(html, pretty_print=True)
print(result)
注:
<meta charset="UTF-8"/>中的/必須要帶,不然會(huì)解析報(bào)錯(cuò)。
- 解析HTML文件
完成了前面的步驟后,便可利用requests庫(kù)來(lái)獲取HTML文件,用Lxml庫(kù)來(lái)解析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 語(yǔ)法
Xpath是一門(mén)在XML文檔中查找信息的語(yǔ)言,對(duì)HTML文檔也有很好的支持。將介紹Xpath的常用語(yǔ)法,Xpath語(yǔ)言在爬蟲(chóng)中的使用技巧。最后通過(guò)案例對(duì)正則表達(dá)式、BeautifulSoup和Lxml進(jìn)行性能對(duì)比。
2.1 節(jié)點(diǎn)關(guān)系
- 父節(jié)點(diǎn)
每個(gè)元素及屬性都有一個(gè)父節(jié)點(diǎn),在下面的例子中,user元素是name、sex、id及goal元素的父節(jié)點(diǎn)。
<user>
<name>xiao ming</name>
<sex>man</sex>
<id>34</id>
<goal>89</goal>
</user>
- 子節(jié)點(diǎn)
元素節(jié)點(diǎn)可有0個(gè)、一個(gè)或多個(gè)子節(jié)點(diǎn),在下面的例子中,name、sex、id及goal元素都是user元素的子節(jié)點(diǎn)。
<user>
<name>xiao ming</name>
<sex>man</sex>
<id>34</id>
<goal>89</goal>
</user>
- 同胞節(jié)點(diǎn)
同胞節(jié)點(diǎn)擁有相同的父節(jié)點(diǎn),在下面的例子中,name、sex、id及goal元素都是同胞節(jié)點(diǎn)
<user>
<name>xiao ming</name>
<sex>man</sex>
<id>34</id>
<goal>89</goal>
</user>
- 先輩節(jié)點(diǎn)
先輩節(jié)點(diǎn)指某節(jié)點(diǎn)的父、父的父節(jié)點(diǎn)等,在下面的例子中,name元素的先輩是user元素和user_database元素:
<user_database>
<user>
<name>xiao ming</name>
<sex>man</sex>
<id>34</id>
<goal>89</goal>
</user>
</user_database>
- 后代節(jié)點(diǎn)
后代節(jié)點(diǎn)指某個(gè)節(jié)點(diǎn)的子節(jié)點(diǎn)、子節(jié)點(diǎn)的子節(jié)點(diǎn)等,在下面的例子中,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é)點(diǎn)選擇
Xpath使用路徑表達(dá)式在XML文檔中選取節(jié)點(diǎn)。節(jié)點(diǎn)是通過(guò)演著路徑或者step來(lái)選取的,如下表:
| 表達(dá)式 | 描述 |
|---|---|
| nodename | 選取此節(jié)點(diǎn)的所有子節(jié)點(diǎn) |
| / | 從根節(jié)點(diǎn)選取 |
| // | 從匹配選擇的當(dāng)前節(jié)點(diǎn)選擇文檔中的節(jié)點(diǎn),而不考慮他們的位置 |
| . | 選取當(dāng)前節(jié)點(diǎn) |
| .. | 選取當(dāng)前節(jié)點(diǎn)的父節(jié)點(diǎn) |
| @ | 選取屬性 |
通過(guò)前面的例子進(jìn)行舉例,如下表所示:
| 表達(dá)式 | 描述 |
|---|---|
| user_database | 選取元素user_database的所有子節(jié)點(diǎn) |
| /user_database | 選取根元素user_database。注釋?zhuān)杭偃缏窂狡鹗加谡备埽?),則此路徑始終代表到某元素的絕對(duì)路徑 |
| user_database/user | 選取屬于user_database的子元素的所有user元素 |
| //user | 選取所有user子元素,而不管它們?cè)谖臋n中的位置 |
| user_database//user | 選取屬于user_database元素的后代的所有user元素,而不管它們位于user_database之下的什么位置 |
| //@attribute | 選取名為attribute的所有屬性 |
Xpath語(yǔ)法中的謂語(yǔ)用來(lái)查找某個(gè)特定的節(jié)點(diǎn)或者包含某個(gè)指定值的節(jié)點(diǎn),謂語(yǔ)被嵌在方括號(hào)中。常見(jiàn)的謂語(yǔ)如下表:
| 路徑表達(dá)式 | 結(jié)果 |
|---|---|
| /user_database/user[1] | 選取屬于user_database子元素的第一個(gè)user元素 |
| //li[@attribute] | 選取所有擁有名為attribute屬性的li元素 |
| //li[@attribute='red'] | 選取所有l(wèi)i元素,且這些元素?fù)碛兄禐閞ed的attribute屬性 |
Xpath中也可以使用通配符來(lái)選取位置的元素,常用的就是“*”通配符,它可以匹配任何元素節(jié)點(diǎn)。
2.3 使用技巧
在爬蟲(chóng)實(shí)戰(zhàn)中,Xpath路徑可以通過(guò)Chrome復(fù)制得到,如下圖:

(1)鼠標(biāo)光標(biāo)定位到想要提取的數(shù)據(jù)位置,右擊,從彈出的快捷菜單中選擇“檢查”命令。
(2)在網(wǎng)頁(yè)源代碼中右擊所選元素。
(3)從彈出的快捷菜單中選擇Copy Xpath命令,這時(shí)便能得到。
//*[@id="qiushi_tag_121174862"]/div[1]/a[2]/h2
通過(guò)代碼即可得到用戶(hù)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)
注意:通過(guò)/text()可以獲取標(biāo)簽中的文字信息。
結(jié)果為
['\n誰(shuí)搶了我微信昵稱(chēng)\n']
上面的結(jié)果為列表的數(shù)據(jù)結(jié)構(gòu),可以通過(guò)切片獲取為字符串?dāng)?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)
當(dāng)需要進(jìn)行用戶(hù)ID的批量爬取時(shí),通過(guò)類(lèi)似于BeautifulSoup中的selector()方法刪除謂語(yǔ)部分是不可行的。這時(shí)的思路為“先抓大后抓小,尋找循環(huán)點(diǎn)”。打開(kāi)Chrome瀏覽器進(jìn)行“檢查”,通過(guò)“三角形符號(hào)”折疊元素,找到每個(gè)段子完整的信息標(biāo)簽,如下圖所示,每一個(gè)div標(biāo)簽為一個(gè)段子信息。

(1)首先通過(guò)復(fù)制構(gòu)造div標(biāo)簽路徑,此時(shí)的路徑為:
//*[@class="article block untagged mb15" ]
這樣就定位到了每個(gè)段子信息,這就是循環(huán)點(diǎn)。
(2)通過(guò)Chrome瀏覽器進(jìn)行“檢查”定位用戶(hù)ID,復(fù)制Xpath到記事本中。
//*[@id="qiushi_tag_121174765"]/div[1]/a[2]/h2
因?yàn)榈谝徊糠譃檠h(huán)部分,將其刪除得到:
div[1]/a[2]/h2
這便是用戶(hù)ID的信息。
注意:這里就不需要斜線(xiàn)作為開(kāi)頭了。
同時(shí)也可以這樣寫(xiě):
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)
# 標(biāo)簽為<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)
有時(shí)候會(huì)遇到相同的字符開(kāi)頭的多個(gè)標(biāo)簽:
<li class="tag-1">需要的內(nèi)容1</li>
<li class="tag-2">需要的內(nèi)容2</li>
<li class="tag-3">需要的內(nèi)容3</li>
想同時(shí)爬取時(shí),不需要構(gòu)造多個(gè)Xpath路徑,通過(guò)starts-with()便可以獲取多個(gè)標(biāo)簽內(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可獲取類(lèi)似標(biāo)簽的信息
上面的示例也可以用starts-with方法來(lái)實(shí)現(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)點(diǎ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)
當(dāng)遇到標(biāo)簽套標(biāo)簽情況時(shí):
<div class="red">需要的內(nèi)容1
<h1>需要的內(nèi)容2</h1>
</div>>
想同時(shí)獲取文本內(nèi)容,可以通過(guò)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(.)方法可用于標(biāo)簽套標(biāo)簽情況

2.4 性能對(duì)比
前面提到Lxml庫(kù)的解析速度快,但是口說(shuō)無(wú)憑,將會(huì)通過(guò)代碼對(duì)正則表達(dá)式、BeautifulSoup、Lxml進(jìn)行性能對(duì)比。
(1)通過(guò)3種方法爬取糗事百科文字內(nèi)容中的信息,如下圖:

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

(3)爬取的數(shù)據(jù)只做返回,不存儲(chǔ)。
代碼如下:
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):
# 用正則表達(dá)式
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> 評(píng)論
comments = re.findall('<i class="number">(\d+)</i> 評(píng)論', 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爬蟲(chóng)
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 檢索出來(lái)的結(jié)果會(huì)有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爬蟲(chóng)
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é)果會(huì)存在一定的差異性。下表總結(jié)了各種爬蟲(chóng)方法的優(yōu)缺點(diǎn):
| 爬取方法 | 性能 | 使用難度 | 安裝難度 |
|---|---|---|---|
| 正則表達(dá)式 | 快 | 困難 | 簡(jiǎn)單(內(nèi)置模塊) |
| BeautifulSoup | 慢 | 剪短 | 簡(jiǎn)單 |
| Lxml | 快 | 簡(jiǎn)單 | 相對(duì)困難 |
當(dāng)網(wǎng)頁(yè)結(jié)構(gòu)簡(jiǎn)單并且想要避免額外依賴(lài)的話(huà)(不需要安裝庫(kù)),使用正則表達(dá)式更為合適。當(dāng)需要爬取的數(shù)據(jù)量較少時(shí),使用較慢的BeautifulSoup也不成問(wèn)題。當(dāng)數(shù)據(jù)量大,需要追求效益時(shí),Lxml是最好的選擇。
3. 綜合案例1----爬取豆瓣網(wǎng)圖書(shū)TOP250的數(shù)據(jù)
將利用Requests和Lxml第三方庫(kù),爬取豆瓣網(wǎng)圖書(shū)TOP250的數(shù)據(jù),并存儲(chǔ)到CSV格式的文件中。
3.1 將數(shù)據(jù)存儲(chǔ)到CSV文件中
前面爬取的數(shù)據(jù)要么打印到屏幕上,要么存儲(chǔ)到TXT文檔中,這些格式并不利于數(shù)據(jù)的存儲(chǔ)。那么大家平時(shí)是用什么來(lái)存儲(chǔ)數(shù)據(jù)的呢?大部分讀者可能是使用微軟公司的Excel來(lái)儲(chǔ)存數(shù)據(jù)的,大規(guī)模的數(shù)據(jù)則是使用數(shù)據(jù)庫(kù)。CSV是存儲(chǔ)表格數(shù)據(jù)的常用文件格式,Excel和很多應(yīng)用都支持CSV格式,因?yàn)樗芎?jiǎn)潔。下面就是一個(gè)CSV文件的例子:
id,name
1,xiaoming
2,zhangsan
3,peter
Python中的csv庫(kù)可以創(chuàng)建CSV文件,并寫(xiě)入數(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'))
# 寫(xiě)入行
這時(shí)的本機(jī)桌面上會(huì)生成名為test的CSV文件,用記事本打開(kāi),效果如下:

3.2 爬蟲(chóng)思路分析
(1) 爬取的內(nèi)容為豆瓣網(wǎng)圖書(shū)TOP250的信息。
(2)爬取豆瓣網(wǎng)圖書(shū)TOP250的10頁(yè)信息,通過(guò)手動(dòng)瀏覽,以下為前4頁(yè)的網(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)需要爬取的信息有:書(shū)名、書(shū)本的URL鏈接、作者、出版社和出版時(shí)間,書(shū)本價(jià)格、評(píng)分和評(píng)價(jià),如下圖:

注意:這里只爬了第一作者
(4)運(yùn)用Python中的csv庫(kù),把爬取的信息存儲(chǔ)在本地的CSV文本中。
3.3 爬蟲(chóng)代碼及分析
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)
# 寫(xiě)入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()
程序運(yùn)行的結(jié)果保存在計(jì)算機(jī)里文件名為doubanbook的csv文件中,如通過(guò)excel打開(kāi)會(huì)出現(xiàn)亂碼錯(cuò)誤,如圖:

可以通過(guò)記事本打開(kāi),將其另存為編碼為UTF-8的文件,便不會(huì)出現(xiàn)亂碼問(wèn)題。

這時(shí)再通過(guò)Excel打開(kāi)文件,便不會(huì)出現(xiàn)亂碼問(wèn)題了。

4. 綜合案例2----爬取起點(diǎn)中文網(wǎng)小說(shuō)信息
將利用Requests 和 Lxml第三方庫(kù),爬取起點(diǎn)中文網(wǎng)小說(shuō)信息,并存儲(chǔ)到Excel文件中。
4.1 將數(shù)據(jù)存儲(chǔ)到Excel文件中
使用Python的第三方庫(kù)xlwt,可將數(shù)據(jù)寫(xiě)入Excel中,通過(guò)PIP進(jìn)行安裝即可。
通過(guò)下面的代碼,便可將數(shù)據(jù)寫(xiě)入Excel中:
import xlwt
# 將數(shù)據(jù)寫(xiě)入Excel的庫(kù)文件中
# 創(chuàng)建工作簿
book = xlwt.Workbook(encoding='utf-8')
# 創(chuàng)建工作表
sheet = book.add_sheet('Sheet1')
# 在相應(yīng)單元格寫(xiě)入數(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')
程序運(yùn)行后,可在本地找到該Excel文件,結(jié)果如圖:

代碼說(shuō)明一下:
(1)導(dǎo)入xlwt庫(kù)
(2)通過(guò)Workbook()方法創(chuàng)建一個(gè)工作簿
(3)創(chuàng)建一個(gè)名字為Sheet1的工作表
(4)寫(xiě)入數(shù)據(jù),可以看出第一個(gè)和第二個(gè)參數(shù)為Excel表格的單元格的位置,第三個(gè)為寫(xiě)入內(nèi)容。
(5)保存到文件
4.2 爬蟲(chóng)思路分析
(1)爬取的內(nèi)容為起點(diǎn)中文網(wǎng)的全部作品信息(https://www.qidian.com/),如下圖

(2)爬取起點(diǎn)中文網(wǎng)的全部作品信息的前100頁(yè),通過(guò)手動(dòng)瀏覽,下面為第2頁(yè)的網(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
猜想這些字段是用來(lái)控制作品分類(lèi)的,我們爬取的為全部作品,依次刪掉一些參數(shù)檢查,發(fā)現(xiàn)將網(wǎng)址改為https://www.qidian.com/all?page=2后,也可以訪(fǎng)問(wèn)相同的信息,通過(guò)多頁(yè)檢驗(yàn),證明了修改的合理性,以此來(lái)構(gòu)造前100頁(yè)URL。
(3)需要爬取的信息有:小說(shuō)名、作者ID、小說(shuō)類(lèi)型、完成情況、摘要和字?jǐn)?shù)。如圖:

(4)運(yùn)用xlwt庫(kù),把爬取的信息存儲(chǔ)在本地的Excel表格中。
# 起點(diǎn)中文網(wǎng)
import xlwt
import requests
from lxml import etree
import time
import re
from fontTools.ttLib import TTFont
# 初始化列表,存入爬蟲(chóng)數(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):
# 定義獲取爬蟲(chóng)信息的函數(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]
# 定位大標(biāo)簽,以此循環(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)):
# 寫(xiě)入表頭
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')
注:代碼寫(xiě)于 2018/11/2,因此關(guān)于字體反爬更新于此時(shí)間段。
程序運(yùn)行后,將會(huì)存入數(shù)據(jù)到Excel表格中,如下圖:
