Python實現爬取貼吧圖片
導讀:
最近周邊朋友學python的越來越多,毫無意外的是,大家都選擇了爬蟲入門。這不難理解。Python有豐富的庫使用,使得爬蟲的實現容易很多,學習之後,回報明顯,容易獲得成就感。總結起來就是:讓人有繼續學下去的慾望。我偏巧例外,先走了Python web。雖然起了個大早,趕了個晚集,但不妨趁清明假期,計劃之外,時間有餘,做一回“願聞其詳”的門外漢。
探一探爬蟲的入門知識,這裡將通過爬取某貼吧的圖片為案例介紹一下簡單的爬蟲需要掌握哪些知識,以及實現的流程。因本人能力有限,若有紕漏處,望各友予以指正,必改。
實驗前準備:
1. 知識點:
urllib: urlencode()
urllib2: HTTPHandler(), ProxyHandler(), build_opener(), Request()
lxml: etree, xpath
2. 推薦工具:
Fiddler4
xpath helper
3. 實現環境:
Ubuntu, python2.7
正文:
1. 什麼是爬蟲
事實上網上有很多對爬蟲高深的解釋,讓人看了雲裡霧裡,我覺得對於初學者,可以一言概之:獲取伺服器的響應資料,並從中分離出自己需要的部分。
當然,不得不說這樣解釋很不嚴謹。可學一門新知識的時候,最重要的是,知道自己在學什麼,至少心中對其有個大致的形象,而不是依稀的模糊的知識。所以入門可以儘可能的簡單理解,往後需要深究的時候再回頭看,必有大悟。
2. 如何獲取伺服器響應資料
既然爬蟲需要獲取伺服器的響應內容,那麼第二步就是如何獲取伺服器資料。
事實上,當我們用瀏覽器開啟百度的時候,瀏覽器就已經在你不知道的情況下,先你的目標伺服器(也就是百度的伺服器)傳送了請求報文(Request Header),如圖:
伺服器通過對請求報文的解析,相應的,會返還你一個響應報文(Response Headers),如圖:
同時會在響應報文中,附帶整個網頁的程式碼,如圖:
詳情可以查閱相關HTTP協議。
所以我們想要獲得伺服器響應的資料,就得儘可能使自己的程式碼執行時,像是尋常的瀏覽器。而伺服器判別的標準,就是從請求報文裡開始的(並非唯一方法
python的庫很強大,所以實現請求報文的構造,傳送,同時獲得響應內容很簡單:
import urllib2
# 目的,也就是網址
url = "https://www.baidu.com/"
# 構建一個http物件
http = urllib2.HTTPHandler()
# 構建一個請求的傳送器
opener = urllib2.build_opener(http)
# 構造一個請求報文
header = {
"User-Agent":"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.140 Safari/537.36"
}
request = urllib2.Request(url, headers = header)
# 傳送請求報文,並且返回響應物件
response = opener.open(request)
print response.read()# 列印物件的內容 (注:header 是個一個字典)3. 對url的分析
首先,我們需要對貼吧“翻頁”理解,貼吧是如何實現翻頁的呢?以李毅吧為例:
可以觀察到的是前面“https://tieba.baidu.com/f?”沒有發生變化。(當然kw="..."和ie="..."也沒有改變,不急,馬上道來)
我們再試試其他貼吧,如美女吧:
有興趣可以多試試其他的貼吧,發現kw值不一樣。事實上url中“?”(問號)之後的內容其實就是瀏覽器向伺服器請求資料的引數,即get,還有一種是post。二者的區別是:get方式傳遞引數會顯示在url當中,而post方式引數放在Form表單中。舉個例子,在人人的登陸頁面,如果鍵入錯誤的資訊,url不會發生改變,瀏覽器像是什麼事兒都沒做一樣,事實上,已經通過Form表單傳遞我們鍵入的密碼以及賬號,並且由伺服器驗證,是否允許登陸(即:賬號密碼正確)。這裡,我們可以通過Fiddler 4 抓包予以驗證
其他的資料可以暫時不管,我所鍵入的資訊,的確有傳送出去。但我們爬貼吧的時候,可以不用理睬這些,因為瀏覽貼吧是以get方式就能完成一切。
回到之間,kw的值,其實是被url編碼過後展示出來的,我們可以通過urllib來實現這個效果:
import urllib
data = {
"kw":"李毅",
"ie":"utf-8",
"pn":"50",
}
kw = urllib.urlencode(data)
print kw
# 輸出結果:
# ie=utf-8&kw=%E6%9D%8E%E6%AF%85&pn=50
同樣,urlencode接受的引數是字典形式,而我們需要傳遞的引數就可以得到瀏覽器所需要的格式了。之後進行拼接成完整的url就可以訪問了。
4. 利用Chrom對網頁分析
回憶一下,當我們需要檢視圖片時,是不是需要進入帖子才行,而翻頁只是為我們提供了更多可以選擇的帖子標題。所以,接下來我們就需要對標題以及帖子的url進行分析。Chrome瀏覽器,右鍵—>檢查—>任意標題:
進入這個帖子檢視url:
按照這個方法多分析幾個,就可以發現進入帖子的url其實就是“https://tieba.baidu.com”+a標籤中href的值,如何取href的值呢?這裡就需要xpath,建議使用xPath player外掛,在網頁進行區配:
from lxml import etree
html = etree.HTML(response.read())
a_href = html.xpath("....") #對應的xPath規則
返回的a_href是個列表,遍歷取出即可。同樣的,進入帖子之後的翻頁也可以同相同的方法:(注:我測試的時候,發現貼吧返回的頁面主題部分被註釋掉了,就會出現明明在頁面上用外掛區配到了結果,但是實際執行的程式碼返回的是空列表,所以可以替換掉頁面中的註釋符號:response.read().replace("<!--", " ").replace("-->", " "))
最後,就該分析照片了,用前面的方法即可。
5. 避免反爬策略:
事實上,保不齊需要爬取的目標網站有反扒策略,那麼應對方法這裡介紹三個:
1. 更換伺服器訪問:即有策略的改變"User-Agent",
ua_list = [
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv2.0.1) Gecko/20100101 Firefox/4.0.1",
"Mozilla/5.0 (Windows NT 6.1; rv2.0.1) Gecko/20100101 Firefox/4.0.1",
"Opera/9.80 (Macintosh; Intel Mac OS X 10.6.8; U; en) Presto/2.8.131 Version/11.11",
"Opera/9.80 (Windows NT 6.1; U; en) Presto/2.8.131 Version/11.11",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_0) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11"
]
2. 休眠:
import time
time,sleep(10)
3. 使用代理ip:
代理ip的請求方式與不使用代理ip不同,但區別很小
# 不使用代理
http = urllib2.HTTPHandler()
opener = urllib2.build_opener(http)
# 使用代理
proxy_http = urllib2.ProxyHandler("http":"192.168.121.111:8080")
opener = urllib2.build_opener(http)
結尾:
最後,貼上完整的程式碼,但是並不是每個貼吧都適用,更多的區別在於不同貼吧中,原始碼標籤名不同。所以同樣的xpath規則可能會得不到想要的結果。需要根據實際情況做出適當的調整。
事實上,在爬蟲當中更多不是技術上的問題,而是策略上的問題。只要是能通過瀏覽器獲得的資料,都可通過爬蟲的方式來得到,所以在爬蟲與反爬蟲中,相信就目前而言,勝利的一定是前者。
# coding:utf-8
import urllib
import urllib2
from lxml import etree
import random
import time
class Down_image(object):
"""
下載需要的,貼吧的圖片
"""
def __init__(self, tieba_name, start_page, end_page):
self.kw = tieba_name
self.s_p = int(start_page)
self.e_p = int(end_page)
self.header = {
"Accept-Language" : "zh-CN,zh;q=0.9",
"Connection" : "keep-alive",
"Host" : "tieba.baidu.com",
}
self.ua_list = [
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv2.0.1) Gecko/20100101 Firefox/4.0.1",
"Mozilla/5.0 (Windows NT 6.1; rv2.0.1) Gecko/20100101 Firefox/4.0.1",
"Opera/9.80 (Macintosh; Intel Mac OS X 10.6.8; U; en) Presto/2.8.131 Version/11.11",
"Opera/9.80 (Windows NT 6.1; U; en) Presto/2.8.131 Version/11.11",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_0) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11"
]
self.times = 0
def start(self):
"""
執行一系列程式碼的按鈕
"""
self.build_url()
def build_url(self):
"""
構建目的貼吧的url
"""
# 構建頁數的url
for p in range(self.s_p-1, self.e_p):
url = "https://tieba.baidu.com/f?"
page_data = {
"kw":self.kw,
"pn":p*50,
}
page_data = urllib.urlencode(page_data)
ful_url = url + page_data
self.get_html(ful_url)
def get_html(self, url):
"""
獲取html,並且解析獲得帖子的內容
"""
handler = urllib2.HTTPHandler()
self.opener = urllib2.build_opener(handler)
request = urllib2.Request(url, headers = self.header)
#獲取隨即user_agent,並且新增在請求報文裡
user_agent = random.choice(self.ua_list)
request.add_header("User-Agent", user_agent)
response = self.opener.open(request)
html = response.read().replace("<!--", "").replace("-->", "")
selector = etree.HTML(html)
links = selector.xpath('//div[@class="threadlist_lz clearfix"]/div/a[@class="j_th_tit "]/@href')
for l in links:
tz_link = "http://tieba.baidu.com" + l + "?pn=1"
self.get_image(tz_link)
def get_image(self, link):
"""
通過傳遞的帖子的link,進入相應的連結裡面來,並且取得圖片的連結
"""
request = urllib2.Request(link, headers = self.header)
user_agent = random.choice(self.ua_list)
request.add_header("User-Agent", user_agent)
html = self.opener.open(request).read().replace("<!--", "").replace("-->", "")
selector = etree.HTML(html)
# 如果是第一頁,那麼就要進行頁數的區分
if link[-1] == str(1):
page_link = selector.xpath('//li[@class="l_pager pager_theme_4 pb_list_pager"]/a/@href')
if page_link:
# 根據對網頁的程式碼分析,得到的翻頁實現方法,對應做出的策略
for p in page_link[:-2]:
p = "http://tieba.baidu.com" + p
self.get_image(p)
# 落落貼/美女吧等
image_link = selector.xpath('//div[@class="d_post_content j_d_post_content "]/img[@class="BDE_Image"]/@src')
# 校花吧
# image_link = selector.xpath('//div[@class="d_post_content j_d_post_content clearfix"]/img[@class="BDE_Image"]/@src')
for i_l in image_link:
i_l = i_l.replace("https", "http")
self.times += 1
if self.times%10 == 0:
print "休息一會"
time.sleep(10)
self.load_image(i_l)
def load_image(self, link):
"""
將圖片下載到本地
"""
filename = link[-10:]
print filename+"正在下載..."
# request = urllib2.Request(link, headers = self.header)
image = urllib2.urlopen(link).read()
# user_agent = random.choice(self.ua_list)
# request.add_header("User-Agent", user_agent)
# image = self.opener.open(request)
with open("girl/"+filename, "wb") as ob:
ob.write(image)
print filename + "下載完成"
def main():
tieba_name = raw_input("輸入你想爬取的貼吧:")
start_page = raw_input("輸入你想爬去的起始頁數:")
end_page = raw_input("輸入你想爬去的結束頁數:")
down_image = Down_image(tieba_name, start_page, end_page)
down_image.start()
print "結束"
if __name__ == "__main__":
main()