本篇涉及知識點:
1瓢姻、xpath語法
2、正則表達式
踩坑:
1音诈、xpath解析出的結(jié)點文本內(nèi)容中文亂碼幻碱。
2、xpath解析時细溅,結(jié)點內(nèi)有多余標簽褥傍,文本被截斷。
3喇聊、用正則表達式匹配的分組輸出亂碼恍风。
發(fā)送請求獲取html文本內(nèi)容
import urllib2
#目標url,這里see_lz=1代表只看樓主誓篱,pn=1代表頁碼為1
url='https://tieba.baidu.com/p/3267113128?see_lz=1&pn=1’
request=urllib2.Request(url)#封裝請求
response=urllib2.urlopen(request)#打開url資源朋贬,得到響應
str_html=response.read()#讀取html文本內(nèi)容
response.close()#關閉資源
print str_html#打印html文本
我們看輸出結(jié)果,這里中文是正常窜骄,沒有亂碼的:
xpath解析并獲取每一個樓層的內(nèi)容
1. 我們先來分析html文本結(jié)構(gòu)
點擊左邊小紅框的按鈕再點擊目標锦募,即可查看到相應的標簽。
可以看到這個div有很明顯的特征邻遏,id=“post_content_56723422722”糠亩,即id包括字段“post_content_”,我們可以直接用contains函數(shù)通過id來找到准验。
tree.xpath('//div[contains(@id,"post_content_")]']
不過為了保險起見赎线,也為了多熟悉一下xpath解析的語法,我們多往上看兩層糊饱。目標是class=“d_post_content_main ”的div下的一個class=“p_content ”的div里的cc標簽下的唯一div垂寥。
所以我們可以用以下語句找到所有樓層的目標內(nèi)容:
tree.xpath('//div[@class="d_post_content_main "]/div[@class="p_content "]/cc/div')
同理我們可以分析結(jié)構(gòu),解析得到相應的樓層號:
tree.xpath('//div[@class="d_post_content_main "]/div[@class="core_reply j_lzl_wrapper"]/div[@class="core_reply_tail clearfix"]/div[@class="post-tail-wrap"]/span[2]')
2. 獲取每個樓層的內(nèi)容
import urllib2
from lxml import html
url='https://tieba.baidu.com/p/3267113128?see_lz=1&pn=1'#目標url
request=urllib2.Request(url)#封裝請求
response=urllib2.urlopen(request)#打開url資源,得到響應
str_html=response.read()#讀取html文本內(nèi)容
response.close()#關閉資源
tree=html.fromstring(str_html)
nodes=tree.xpath('//div[@class="d_post_content_main "]')#先找的所有的樓層再進一步解析樓層號碼和內(nèi)容
for node in nodes:
layer=node.xpath('div[@class="core_reply j_lzl_wrapper"]/div[@class="core_reply_tail clearfix"]/div[@class="post-tail-wrap"]/span[2]')[0].text
print layer#輸出樓層號
content=node.xpath('div[@class="p_content "]/cc/div')[0].text
print content#輸出樓層內(nèi)容
這里就出現(xiàn)第一個坑了矫废,前面輸出html文本是沒有中文亂碼的盏缤,這里xpath解析完以后輸出就中文亂碼了。運行一下看輸出結(jié)果:
不過在python里內(nèi)置了unicode字符類型蓖扑,這是解決亂碼問題的神器唉铜。將str類型的字符串轉(zhuǎn)換成unicode字符類型就可以了。轉(zhuǎn)換方法有兩種:
s1 = u"字符串"
s2 = unicode("字符串", "utf-8")
想具體了解unicode和python里的亂碼問題的律杠,可以參考這一篇博客:關于Python的編碼潭流、亂碼以及Unicode的一些研究
下面看修改后的代碼:
注:因為一樓的div的class有兩個,所以將獲取每個樓層結(jié)點的代碼改成有contains函數(shù)
import urllib2
from lxml import html
url='https://tieba.baidu.com/p/3267113128?see_lz=1&pn=1'#目標url
request=urllib2.Request(url)#封裝請求
response=urllib2.urlopen(request)#打開url資源柜去,得到響應
str_html=response.read()#讀取html文本內(nèi)容
response.close()#關閉資源
str_html=unicode(str_html,'utf-8')#將string字符類型轉(zhuǎn)換成unicode字符類型
tree=html.fromstring(str_html)
nodes=tree.xpath('//div[contains(@class,"d_post_content_main")]')#先找的所有的樓層再進一步解析樓層號碼和內(nèi)容
for node in nodes:
layer=node.xpath('div[@class="core_reply j_lzl_wrapper"]/div[@class="core_reply_tail clearfix"]/div[@class="post-tail-wrap"]/span[2]')[0].text
print layer#輸出樓層號
content=node.xpath('div[@class="p_content "]/cc/div')[0].text
print content#輸出樓層內(nèi)容
運行可以看到結(jié)果里有一些樓層的輸出內(nèi)容不完整灰嫉,這就是第二個坑:
我們返回瀏覽器查看結(jié)構(gòu),可以發(fā)現(xiàn)嗓奢,原來是這個div里有其他標簽(a和br)讼撒,不是純文本的。這里就可以用string函數(shù)來過濾掉多余的標簽股耽。
在前面的代碼里根盒,輸出樓層的語句換成:
content=node.xpath('div[@class="p_content "]/cc/div')[0]
content_txt=content.xpath('string(.)')#用string函數(shù)過濾掉多余子標簽,.代表本結(jié)點
print content_txt#輸出樓層內(nèi)容
但是br也被過濾掉了物蝙,有些樓層有分序號的輸出結(jié)果也是一行炎滞,看著不方便。我們可以用正則表達式匹配上數(shù)字序號诬乞,并在之前插入換行符“\n”册赛。re模塊的sub是匹配并替換字符串的方法。python中正則表達式的更多使用可以參考Python正則表達式指南震嫉。
re_num=re.compile('([0-9]\.)') #匹配所有的數(shù)字序號(數(shù)字后通常跟".")森瘪,加上括號作為分組1,這樣可以進行替換的時候用上
for node in nodes:
layer=node.xpath('div[@class="core_reply j_lzl_wrapper"]/div[@class="core_reply_tail clearfix"]/div[@class="post-tail-wrap"]/span[2]')[0].text
print layer#輸出樓層號
content=node.xpath('div[@class="p_content "]/cc/div')[0]
content_txt=content.xpath('string(.)')
content_txt=re.sub(re_num,'\n\1',content_txt) #將目標匹配替換成換行符+本身分組(也就是分組1)
print content_txt #輸出樓層內(nèi)容
但輸出結(jié)果顯示责掏,換行成功了柜砾,數(shù)字序號成了亂碼。這里是第三個坑换衬,漏掉了一個小地方。
修改倒數(shù)第二句為:
content_txt=re.sub(re_num,r'\n\1',content_txt)#加上一個字母'r'
這個“r”是代表防止字符轉(zhuǎn)義证芭,也就是“original”原生字符瞳浦。關于正則中字符的轉(zhuǎn)義,有興趣的同學可以google废士、百度叫潦。
接下來我們簡單將代碼寫成面向?qū)ο蟮娘L格。完整代碼如下:
import urllib2
from lxml import html
import re
class BaiduTieba:
url_base='https://tieba.baidu.com/p/3267113128?see_lz='
file = open("../text/bdtb.txt","w+")
def __init__(self,see_lz):#see_lz=1表示只看樓主
self.url_base=self.url_base+see_lz
def setPage(self,page):
self.url=self.url_base+'&pn='+page#目標url
def printPage(self):
request=urllib2.Request(self.url)#封裝請求
response=urllib2.urlopen(request)#打開url資源官硝,得到響應
str_html=response.read()#讀取html文本內(nèi)容
response.close()#關閉資源
str_html=unicode(str_html,'utf-8')#將string字符類型轉(zhuǎn)換成unicode字符類型
tree=html.fromstring(str_html)
nodes=tree.xpath('//div[contains(@class,"d_post_content_main")]')#先找的所有的樓層再進一步解析樓層號碼和內(nèi)容
re_num=re.compile('([0-9]\.)')
for node in nodes:
layer=node.xpath('div[@class="core_reply j_lzl_wrapper"]/div[@class="core_reply_tail clearfix"]/div[@class="post-tail-wrap"]/span[2]')[0].text
self.file.write("\n")
self.file.write("vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv\n")
self.file.write("------------------------------------------------------------------------------------\n")
self.file.write(" "+layer.encode("utf-8")+" \n")
self.file.write("------------------------------------------------------------------------------------\n")
content=node.xpath('div[@class="p_content "]/cc/div')[0]
content_txt=content.xpath('string(.)')
content_txt=re.sub(re_num,r'\n\1',content_txt)
self.file.write(content_txt.encode("utf-8"))#輸出樓層內(nèi)容
self.file.write("------------------------------------------------------------------------------------\n")
crawl_bdtb=BaiduTieba("1")
for i in range(5):#爬5頁
crawl_bdtb.setPage(str(i+1))
crawl_bdtb.printPage()
爬取結(jié)果截圖: