您的位置:首頁>正文

python爬蟲入門---爬百度貼吧

本篇涉及知識點:1、xpath語法2、規則運算式

踩坑:1、xpath解析出的結點文本內容中文亂碼。 2、xpath解析時, 結點內有多餘標籤, 文本被截斷。 3、用規則運算式匹配的分組輸出亂碼。

發送請求獲取html文本內容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)#封裝請求 respmock=urllib2.urlopen(request)#打開url資源, 得到回應 str_html=response.read#讀取html文本內容 response.close#關閉資源 print str_html#列印html文本

我們看輸出結果, 這裡中文是正常, 沒有亂碼的:

html文本輸出

xpath解析並獲取每一個樓層的內容1. 我們先來分析html文本結構

點擊左邊小紅框的按鈕再點擊目標, 即可查看到相應的標籤。

樓層內容的標籤

可以看到這個div有很明顯的特徵, id=“post_content_56723422722”, 即id包括欄位“post_content_”, 我們可以直接用contains函數通過id來找到。

tree.xpath('//div[contains(@id,"post_content_")]']

不過為了保險起見, 也為了多熟悉一下xpath解析的語法, 我們多往上看兩層。 目標是class=“d_post_content_main ”的div下的一個class=“p_content ”的div裡的cc標籤下的唯一div。 所以我們可以用以下語句找到所有樓層的目標內容:

tree.xpath('//div[@class="d_post_content_main "]/div[@class="p_content "]/cc/div')

同理我們可以分析結構, 解析得到相應的樓層號:

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]')

下面展示完整代碼:

import urllib2 from lxml import html url='https://tieba.baidu.com/p/3267113128?see_lz=1&pn=1'#目標url request=urllib2.Request(url)#封裝請求 respmock=urllib2.urlopen(request)#打開url資源, 得到回應 str_html=response.read#讀取html文本內容 response.close#關閉資源 tree=html.fromstring(str_html) nodes=tree.xpath('//div[@class="d_post_content_main "]')#先找的所有的樓層再進一步解析樓層號碼和內容 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#輸出樓層號 cmock=node.xpath('div[@class="p_content "]/cc/div')[0].text print content#輸出樓層內容

這裡就出現第一個坑了, 前面輸出html文本是沒有中文亂碼的, 這裡xpath解析完以後輸出就中文亂碼了。

運行一下看輸出結果:

xpath解析中文亂碼

不過在python裡內置了unicode字元類型, 這是解決亂碼問題的神器。 將str類型的字串轉換成unicode字元類型就可以了。 轉換方法有兩種:

s1 = u"字串" s2 = unicode("字串", "utf-8")

想具體瞭解unicode和python裡的亂碼問題的, 可以參考這一篇博客:關於Python的編碼、亂碼以及Unicode的一些研究

下面看修改後的代碼:注:因為一樓的div的class有兩個, 所以將獲取每個樓層結點的代碼改成有contains函數

import urllib2 from lxml import html url='https://tieba.baidu.com/p/3267113128?see_lz=1&pn=1'#目標url request=urllib2.Request(url)#封裝請求 respmock=urllib2.urlopen(request)#打開url資源, 得到回應 str_html=response.read#讀取html文本內容 response.close#關閉資源 str_html=unicode(str_html,'utf-8')#將string字元類型轉換成unicode字元類型 tree=html.fromstring(str_html) nodes=tree.xpath('//div[contains(@class,"d_post_content_main")]')#先找的所有的樓層再進一步解析樓層號碼和內容 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#輸出樓層號 cmock=node.xpath('div[@class="p_content "]/cc/div')[0].text print content#輸出樓層內容

運行可以看到結果裡有一些樓層的輸出內容不完整, 這就是第二個坑:

樓層不完整

我們返回流覽器查看結構, 可以發現, 原來是這個div裡有其他標籤(a和br), 不是純文字的。 這裡就可以用string函數來過濾掉多餘的標籤。

在前面的代碼裡, 輸出樓層的語句換成:

cmock=node.xpath('div[@class="p_content "]/cc/div')[0] content_txt=content.xpath('string(.)')#用string函數過濾掉多餘子標籤, .代表本結點 print content_txt#輸出樓層內容

但是br也被過濾掉了, 有些樓層有分序號的輸出結果也是一行, 看著不方便。 我們可以用規則運算式匹配上數字序號, 並在之前插入分行符號“ ”。

re_num=re.compile('([0-9].)') #匹配所有的數位序號(數位後通常跟"."), 加上括弧作為分組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#輸出樓層號 cmock=node.xpath('div[@class="p_content "]/cc/div')[0] content_txt=content.xpath('string(.)') content_txt=re.sub(re_num,' ',content_txt) #將目標匹配替換成分行符號+本身分組(也就是分組1) print content_txt #輸出樓層內容

但輸出結果顯示, 換行成功了,數字序號成了亂碼。這裡是第三個坑,漏掉了一個小地方。

修改倒數第二句為:

content_txt=re.sub(re_num,r' ',content_txt)#加上一個字母'r'

這個“r”是代表防止字元轉義,也就是“original”原生字元。關於正則中字元的轉義,有興趣的同學可以google、百度。

接下來我們簡單將代碼寫成物件導向的風格。完整代碼如下:

import urllib2 from lxml import html import re class BaiduTieba: url_base='https://tieba.baidu.com/p/3267113128?see_lz=' file = open("bdtb.txt","w+")#用來寫入爬取結果 def __init__(self,see_lz): 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)#封裝請求 respmock=urllib2.urlopen(request)#打開url資源,得到回應 str_html=response.read#讀取html文本內容 response.close#關閉資源 str_html=unicode(str_html,'utf-8')#將string字元類型轉換成unicode字元類型 tree=html.fromstring(str_html) nodes=tree.xpath('//div[contains(@class,"d_post_content_main")]')#先找的所有的樓層再進一步解析樓層號碼和內容 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(" ") self.file.write("vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv ") self.file.write("------------------------------------------------------------------------------------ ") self.file.write(" "+layer.encode("utf-8")+" ") self.file.write("------------------------------------------------------------------------------------ ") cmock=node.xpath('div[@class="p_content "]/cc/div')[0] content_txt=content.xpath('string(.)') content_txt=re.sub(re_num,r' ',content_txt) self.file.write(content_txt.encode("utf-8"))#輸出樓層內容 self.file.write("------------------------------------------------------------------------------------ ") crawl_bdtb=BaiduTieba("1") for i in range(5): crawl_bdtb.setPage(str(i+1)) crawl_bdtb.printPage

爬取結果截圖:

爬取結果

換行成功了,數字序號成了亂碼。這裡是第三個坑,漏掉了一個小地方。

修改倒數第二句為:

content_txt=re.sub(re_num,r' ',content_txt)#加上一個字母'r'

這個“r”是代表防止字元轉義,也就是“original”原生字元。關於正則中字元的轉義,有興趣的同學可以google、百度。

接下來我們簡單將代碼寫成物件導向的風格。完整代碼如下:

import urllib2 from lxml import html import re class BaiduTieba: url_base='https://tieba.baidu.com/p/3267113128?see_lz=' file = open("bdtb.txt","w+")#用來寫入爬取結果 def __init__(self,see_lz): 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)#封裝請求 respmock=urllib2.urlopen(request)#打開url資源,得到回應 str_html=response.read#讀取html文本內容 response.close#關閉資源 str_html=unicode(str_html,'utf-8')#將string字元類型轉換成unicode字元類型 tree=html.fromstring(str_html) nodes=tree.xpath('//div[contains(@class,"d_post_content_main")]')#先找的所有的樓層再進一步解析樓層號碼和內容 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(" ") self.file.write("vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv ") self.file.write("------------------------------------------------------------------------------------ ") self.file.write(" "+layer.encode("utf-8")+" ") self.file.write("------------------------------------------------------------------------------------ ") cmock=node.xpath('div[@class="p_content "]/cc/div')[0] content_txt=content.xpath('string(.)') content_txt=re.sub(re_num,r' ',content_txt) self.file.write(content_txt.encode("utf-8"))#輸出樓層內容 self.file.write("------------------------------------------------------------------------------------ ") crawl_bdtb=BaiduTieba("1") for i in range(5): crawl_bdtb.setPage(str(i+1)) crawl_bdtb.printPage

爬取結果截圖:

爬取結果

同類文章
Next Article
喜欢就按个赞吧!!!
点击关闭提示