概要
Lxml庫
是基于libxml2
的XML解析庫
的Python
封裝起宽。該模塊使用C
語言編寫陪拘,解析速度比BeautifulSoup
更快磕仅。Lxml庫
使用Xpath
語法解析定位網(wǎng)頁數(shù)據(jù)荐吉。
將講解Lxml庫在Mac
和Linux
環(huán)境中的安裝方法,還將介紹Lxml庫
的使用方法及Xpath
的語法知識炬藤,而且通過案例對正則表達式
、BeautifulSoup
和Lxml
進行性能對比碴里。
主要涉及的知識點:
Lxml庫:學會各個系統(tǒng)下Lxml庫的安裝和使用方法沈矿。
Xpath語法:學會Xpath語法并通過Xpath語法提取所需的網(wǎng)頁信息。
性能對比:通過案例對
正則表達式
咬腋、BeautifulSoup
和Lxml
進行性能對比羹膳。Requests和Lxml庫組合應(yīng)用:演示如何利用這兩大庫進行爬蟲的方法和技巧。
1. Lxml庫的安裝與使用方法
Lxml庫解析網(wǎng)頁數(shù)據(jù)快根竿,但安裝過程卻相對困難陵像。主要講解Lxml庫在Mac
和Linux
環(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é)果如下:
這里體現(xiàn)了Lxml
庫一個非常使用的功能就是自動修正HTML
代碼,應(yīng)該注意到了最后一個li
標簽溯饵,其實是把尾標簽刪掉了侵俗,是不閉合的。不過Lxml
因為集成了libxml2
的特性丰刊,具有自動修正HTML
代碼的功能隘谣,這里不僅補齊了li
標簽,而且還添加了html
和body
標簽。
- 讀取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
殖熟、sex
、id
及goal
元素的父節(jié)點斑响。
<user>
<name>xiao ming</name>
<sex>man</sex>
<id>34</id>
<goal>89</goal>
</user>
- 子節(jié)點
元素節(jié)點可有0個菱属、一個或多個子節(jié)點,在下面的例子中舰罚,name
纽门、sex
、id
及goal
元素都是user元素的子節(jié)點营罢。
<user>
<name>xiao ming</name>
<sex>man</sex>
<id>34</id>
<goal>89</goal>
</user>
- 同胞節(jié)點
同胞節(jié)點擁有相同的父節(jié)點赏陵,在下面的例子中,name
、sex
蝙搔、id
及goal
元素都是同胞節(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復制得到,如下圖:
(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標簽為一個段子信息幌陕。
(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(.)方法可用于標簽套標簽情況
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)亂碼問題了。
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
表格中骤素,如下圖: