通過Python爬蟲爬取知乎某個問題下的圖片
阿新 • • 發佈:2019-01-28
該爬蟲的完整程式碼我把它放到了GitHub上,因為目前是在一點點的增加功能階段,所以程式碼可能沒有完善好,但是正常執行時沒有問題的,歡迎拍磚,:)
該爬蟲主要是通過requests來實現的,該模組完全可以很好的代替urllib和urllib2,而且功能更強大,詳細可以看這裡。同時也用到了pillow模組中的image物件,實現環境是Python2,不過在Python3上只需很小的改動就可以正常執行,等後續程式碼功能完善好後,我會把Python3的實現也整理一份出來放在GitHub上。
首先通過cookie模擬登陸到知乎,然後獲取知乎某一個問題的連結,開啟並獲取該問題回答下的圖片,然後儲存到本地。我們先看下知乎中的網頁html文字,
對於某一個使用者的回答是這樣的:紅色方框中的標籤是回答的具體內容標籤
然後原始圖片是在下面這個標籤裡,包含在上圖紅色方框的標籤下:
我們在寫正則表示式的時候只需匹配到這個標籤,然後取出裡面的url就可以了。具體的正則表示式如下,分為兩部分,首先取出”zm-editable-content.."標籤裡的全部內容,然後在從中取出"data-actualsrc"的內容:
pattern = re.compile('<a class="author-link".*?<span title=.*?<div class="zh-summary.*?' + '<div class="zm-editable-content.*?>(.*?)</div>', re.S)
pattern = re.compile('data-actualsrc="(.*?)">', re.S)
然後我們通過模擬登陸到知乎,具體的模擬登陸解釋可以看這裡。
# -*-coding:utf-8 -*- import requests from requests.adapters import HTTPAdapter # import urllib # import urllib2 import cookielib import re import time import os.path from PIL import Image user_agent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_5)' headers = {'User-Agent': user_agent} session = requests.session() session.cookies = cookielib.LWPCookieJar(filename='cookies') try: session.cookies.load(ignore_discard=True) except: print "Cookie 未能載入" def get_xsrf(): '''_xsrf 是一個動態變化的引數''' index_url = "http://www.zhihu.com" index_page = session.get(index_url, headers=headers) html = index_page.text pattern = r'name="_xsrf" value="(.*?)"' _xsrf = re.findall(pattern, html) return _xsrf[0] def get_captcha(): t = str(int(time.time() * 1000)) captcha_url = 'http://www.zhihu.com/captcha.gif?r' + t + "&type=login" print captcha_url r = session.get(captcha_url, headers = headers) with open('captcha.jpg', 'wb') as f: f.write(r.content) f.close() try: im = Image.open('captcha.jpg') im.show() im.close() except: print u'captcha.jpg 所在目錄:%s, 手動輸入'% os.path.abspath('captcha.jpg') captcha = input("input captcha\n") return captcha def isLogin(): url = "https://www.zhihu.com/settings/profile" login_code = session.get(url, allow_redirects=False).status_code print "login code: ", login_code if int(x=login_code) == 200: return True else: return False def login(secret, account): if isLogin(): print "已經登入" return if re.match(r"^1\d{10}$", account): print "手機號登陸\n" post_url = 'http://www.zhihu.com/login/phone_num' postdata = { '_xsrf': get_xsrf(), 'password': secret, 'remember_me': 'true', 'phone_num': account, } else: print '郵箱登入\n' post_url = 'http://www.zhihu.com/login/email' postdata = { '_xsrf': get_xsrf(), 'password': secret, 'remember_me': 'true', 'email': account, } try: login_page = session.post(post_url, data=postdata, headers=headers) login_code = login_page.text print login_page.status print login_code print 'what?' except: print '需要驗證碼' postdata['captcha'] = get_captcha() login_page = session.post(post_url, data=postdata, headers=headers) login_code = eval(login_page.text) #eval 從字串中提取字典 u = login_code['msg'] session.cookies.save() def getPageCode(pageUrl): try: req = session.get(pageUrl, headers=headers) print req.request.headers return req.text except urllib2.URLError, e: if hasattr(e, 'reason'): print u"開啟連結失敗...", e.reason return None
登陸進去後,我們在開啟某一個知乎問題連結,爬取裡面的圖片然後下載到本地目錄,具體看下面的程式碼:注意在輸入驗證碼的時候我們用的是input(),在Python2中用input()輸入的時候,如果輸入字串,那麼要在輸入的字元上加上引號,否則會報錯,如:“abcd"
def getPageCode(pageUrl):
try:
req = session.get(pageUrl, headers=headers)
print req.request.headers
return req.text
except urllib2.URLError, e:
if hasattr(e, 'reason'):
print u"開啟連結失敗...", e.reason
return None
def getImageUrl(pageUrl):
pageCode = getPageCode(pageUrl)
if not pageCode:
print "開啟網頁連結失敗.."
return None
pattern = re.compile('<a class="author-link".*?<span title=.*?<div class="zh-summary.*?' +
'<div class="zm-editable-content.*?>(.*?)</div>', re.S)
items = re.findall(pattern, pageCode)
imagesUrl = []
pattern = re.compile('data-actualsrc="(.*?)">', re.S)
for item in items:
urls = re.findall(pattern, item)
imagesUrl.extend(urls)
for url in imagesUrl:
print url
return imagesUrl
def saveImagesFromUrl(pageUrl, filePath):
imagesUrl = getImageUrl(pageUrl)
if not imagesUrl:
print 'imagesUrl is empty'
return
nameNumber = 0;
for image in imagesUrl:
suffixNum = image.rfind('.')
suffix = image[suffixNum:]
fileName = filePath + os.sep + str(nameNumber) + suffix
nameNumber += 1
print 'save in: ', fileName
response = requests.get(image)
contents = response.content
try:
with open(fileName, "wb") as pic:
pic.write(contents)
except IOError:
print 'Io error'
login('這裡是密碼','這裡是你的知乎賬戶')
saveImagesFromUrl('https://www.zhihu.com/question/46435597', '/Volumes/HDD/Picture')
注:該程式碼目前只能爬取到知乎某個問題下第一頁的回答內容。最後就可以把圖片儲存到Picture這個目錄下了,當然這個爬蟲目前還可以做很多的改動,比如翻頁功能,然後多執行緒下載之類的,後續改進後我在貼上來吧。
=====================更新1:下面來增加爬取知乎時的翻頁
知乎網在處理翻頁的時候,不像糗事百科這種直接在網址後面加數字一二三就可以實現翻頁了,而是向伺服器傳送post請求,然後伺服器響應翻頁請求。我們用谷歌開發者工具抓取來看看就清楚了,下面是開啟某個問題第一頁時的獲取情況:
這裡我們可以發現,在請求頁面的時候,是向伺服器請求一個連線,然後post的資料有method和params,然後我們把頁面下拉到最下面點選載入更多時如下:
這是第一個QuestionAnswerListV2,是在我點選載入更多的時候產生的,裡面的資料內容和第一個的基本一樣,只是下面的引數“offset”偏移量不同,增加了10,可以看出在一個頁面上顯示了10條資料,當然這得是在我們登陸的情況下,沒有登陸的話就無法繼續往下執行了。
所以到這裡我們就已經搞清楚了,在翻頁時需要post的連結和data,那處理起來就輕鬆多了,下面就直接貼程式碼了,執行後有彩蛋喔,模擬登陸部分還是沒變。
注意:下面這段程式碼雖然可以正常的使用了,而且設定了連結超時和重連,不過當你爬取的問題的回答數比較多的時候,可能會等得久一點。
def getImageUrl():
url = "https://www.zhihu.com/node/QuestionAnswerListV2"
method = 'next'
size = 10
allImageUrl = []
#迴圈直至爬完整個問題的回答
while(True):
print '===========offset: ', size
postdata = {
'method': 'next',
'params': '{"url_token":' + str(46435597) + ',"pagesize": "10",' +\
'"offset":' + str(size) + "}",
'_xsrf':get_xsrf(),
}
size += 10
page = session.post(url, headers=headers, data=postdata)
ret = eval(page.text)
listMsg = ret['msg']
if not listMsg:
print "圖片URL獲取完畢, 頁數: ", (size-10)/10
return allImageUrl
pattern = re.compile('data-actualsrc="(.*?)">', re.S)
for pageUrl in listMsg:
items = re.findall(pattern, pageUrl)
for item in items: #這裡去掉得到的圖片URL中的轉義字元'\\'
imageUrl = item.replace("\\", "")
allImageUrl.append(imageUrl)
def saveImagesFromUrl(filePath):
imagesUrl = getImageUrl()
print "圖片數: ", len(imageUrl)
if not imagesUrl:
print 'imagesUrl is empty'
return
nameNumber = 0;
for image in imagesUrl:
suffixNum = image.rfind('.')
suffix = image[suffixNum:]
fileName = filePath + os.sep + str(nameNumber) + suffix
nameNumber += 1
try:
# 設定超時重試次數及超時時間單位秒
session.mount(image, HTTPAdapter(max_retries=3))
response = session.get(image, timeout=20)
contents = response.content
with open(fileName, "wb") as pic:
pic.write(contents)
except IOError:
print 'Io error'
except requests.exceptions.ConnectionError:
print '連線超時,URL: ', image
print '圖片下載完畢'
login('這是你的知乎密碼','這是你的知乎賬戶')
saveImagesFromUrl('/Volumes/HDD/Picture')
下面這段程式碼改進了一下,開了兩個執行緒來跑,就是一個簡單的生產者消費者模型,一個執行緒負責從網頁中提取圖片的URL,一個執行緒負責下載和儲存到檔案中。執行起來沒有問題,測試了下當爬到一千多張的時候就會有錯,我猜想是知乎網做了反爬蟲處理,所以後面的更新中打算採用代理來繞開伺服器的攔截。
from threading import Thread
from Queue import Queue
queue = Queue(50)
filePath = '/Volumes/HDD/image'
isRun = True
class GetImageURLThread(Thread):
def run(self):
url = "https://www.zhihu.com/node/QuestionAnswerListV2"
method = 'next'
size = 10
if not os.path.exists(filePath):
os.makedirs(filePath)
# 迴圈直至爬完整個問題的回答
while (True):
print '===========offset: ', size
postdata = {
'method': 'next',
'params': '{"url_token":' + str(34243513) + ',"pagesize": "10",' + \
'"offset":' + str(size) + "}",
'_xsrf': get_xsrf(),
}
size += 10
page = session.post(url, headers=headers, data=postdata)
ret = eval(page.text)
listMsg = ret['msg']
if not listMsg:
print "圖片URL獲取完畢, 頁數: ", (size-10) / 10
queue.join()
isRun = False
break
pattern = re.compile('data-actualsrc="(.*?)">', re.S)
global queue
for pageUrl in listMsg:
items = re.findall(pattern, pageUrl)
for item in items: # 這裡去掉得到的圖片URL中的轉義字元'\\'
imageUrl = item.replace("\\", "")
queue.put(imageUrl)
class DownloadImgAndWriteToFile(Thread):
def run(self):
nameNumber = 0
global queue
while isRun:
image = queue.get()
queue.task_done()
suffixNum = image.rfind('.')
suffix = image[suffixNum:]
fileName = filePath + os.sep + str(nameNumber) + suffix
nameNumber += 1
try:
# 設定超時重試次數及超時時間單位秒
session.mount(image, HTTPAdapter(max_retries=3))
response = session.get(image, timeout=20)
contents = response.content
with open(fileName, "wb") as pic:
pic.write(contents)
except requests.exceptions.ConnectionError:
print '連線超時,URL: ', image
except IOError:
print 'Io error'
print '圖片下載完畢'
if __name__ == '__main__':
login('這是你的知乎密碼', '這是你的知乎賬戶')
urlThread = GetImageURLThread()
downloadThread = DownloadImgAndWriteToFile()
urlThread.start()
downloadThread.start()
urlThread.join()
downloadThread.join()
===============更新2,設定代理,Python爬蟲設定代理IP爬取知乎圖片
效果類似這樣: