起
上次說了要爬知乎的圖片,于是花了一下午的時間去完成這件事,發(fā)現(xiàn)暫時接觸到的爬蟲總是逃脫不了一個規(guī)律:
- 模擬登陸
- 獲取真實網(wǎng)頁HTML源代碼
- 解析獲取到的網(wǎng)頁源代碼
- 獲取想要的資源(下載到某個文件夾或者輸出到表格中整合起來)
也許和我說的有一些出入眼耀,應(yīng)該是剛學(xué)這個東西的原因,接下來還想研究一下多線程爬蟲佩憾、添加代理哮伟、爬取海量數(shù)據(jù)并整合成圖標(biāo)形式,先把能做的做了妄帘。
承
因為是在上一次的基礎(chǔ)上進行的楞黄,所以沒有看上一篇文章的可以先看一下,這里用到的工具跟之前一樣:
- win7 64位 旗艦版
- Python 3.5 64-bit
- PyCharm
這里模擬登陸是跟之前一樣的代碼抡驼,直接貼就是:
logn_url = 'http://www.zhihu.com/#signin'
session = requests.session()
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36',
}
content = session.get(logn_url, headers=headers).content
soup = BeautifulSoup(content, 'html.parser')
def getxsrf():
return soup.find('input', attrs={'name': "_xsrf"})['value']
# 獲取驗證碼
def get_captcha():
t = str(int(time.time() * 1000))
captcha_url = 'http://www.zhihu.com/captcha.gif?r=' + t + "&type=login"
r = session.get(captcha_url, headers=headers)
with open('captcha.jpg', 'wb') as f:
f.write(r.content)
f.close()
# 用pillow 的 Image 顯示驗證碼
# 如果沒有安裝 pillow 到源代碼所在的目錄去找到驗證碼然后手動輸入
try:
im = Image.open('captcha.jpg')
im.show()
im.close()
except:
print(u'請到 %s 目錄找到captcha.jpg 手動輸入' % os.path.abspath('captcha.jpg'))
captcha = input("please input the captcha\n>")
return captcha
def isLogin():
# 通過查看用戶個人信息來判斷是否已經(jīng)登錄
url = "https://www.zhihu.com/settings/profile"
login_code = session.get(url, allow_redirects=False).status_code
if int(x=login_code) == 200:
return True
else:
return False
def login(secret, account):
# 通過輸入的用戶名判斷是否是手機號
if re.match(r"^1\d{10}$", account):
print("手機號登錄 \n")
post_url = 'http://www.zhihu.com/login/phone_num'
postdata = {
'_xsrf': getxsrf(),
'password': secret,
'remember_me': 'true',
'phone_num': account,
}
else:
print("郵箱登錄 \n")
post_url = 'http://www.zhihu.com/login/email'
postdata = {
'_xsrf': getxsrf(),
'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)
except:
# 需要輸入驗證碼后才能登錄成功
postdata["captcha"] = get_captcha()
login_page = session.post(post_url, data=postdata, headers=headers)
login_code = eval(login_page.text)
print(login_code['msg'])
這里的代碼來自GitHub上的fuck-login項目鬼廓,在此表示感謝,我在原始代碼上進行了改進致盟,原始代碼是適配了Python2.x和Python3.x碎税,但是我學(xué)的是Python3.x所以去掉了一些我沒用過的模塊,也就是說我改進了后的代碼是適用于Python3.x的馏锡。
下面就是準(zhǔn)備獲取圖片了雷蹂,先找一個目標(biāo),最近有一個問題很火:
還記得我列出來的步驟么萎河,模擬登陸之后是獲取真實的網(wǎng)頁源代碼荔泳,什么叫真實的蕉饼,這個問題問得好虐杯,你沒發(fā)現(xiàn)知乎很喜歡用動態(tài)加載技術(shù)么,也就是說昧港,你看到的只是表象擎椰,這里也一樣。
轉(zhuǎn)
來创肥,我們先點贊數(shù)最高的妹子上傳的圖片:
咳咳咳达舒,好像跑偏了,我們的目標(biāo)是星辰大海叹侄,正確的做法是鼠標(biāo)右鍵查看網(wǎng)頁源代碼:
是不是看到了很多圖片鏈接巩搏,當(dāng)然我們要找.jpg、.jpeg趾代、.png后綴的:
[站外圖片上傳中……(8)]
這里有兩個:data-original
和data-actualsrc
贯底,實際查看的圖片是data-original
的圖片比data-actualsrc
的大,下載下來也是如此撒强,但是因為是使用正則去匹配規(guī)則禽捆,而data-original
有多項,上面代碼只是貼出來的一部分飘哨,實際匹配的結(jié)果類似這樣:
data-actualsrc
data-actualsrc="https://pic2.zhimg.com/be7600989233bdf438e5ba23f2cdb685_b.jpg">
data-actualsrc="https://pic2.zhimg.com/b6274542f3785c27ab4a38d4db906efd_b.jpg">
data-original
data-original="https://pic2.zhimg.com/be7600989233bdf438e5ba23f2cdb685_r.jpg">
data-original="https://pic2.zhimg.com/be7600989233bdf438e5ba23f2cdb685_r.jpg" data-actualsrc="https://pic2.zhimg.com/be7600989233bdf438e5ba23f2cdb685_b.jpg">
data-original="https://pic2.zhimg.com/b6274542f3785c27ab4a38d4db906efd_r.jpg">
data-original="https://pic2.zhimg.com/b6274542f3785c27ab4a38d4db906efd_r.jpg" data-actualsrc="https://pic2.zhimg.com/b6274542f3785c27ab4a38d4db906efd_b.jpg">
data-original="https://pic2.zhimg.com/0930549116d22ffce22e98c32683d621_r.jpg">
這是在同一段網(wǎng)頁源碼測試下的結(jié)果胚想,匹配后一種會得到多個相同的url地址,解析起來也更麻煩芽隆,這也跟正則寫的簡單有關(guān)系浊服,有興趣的可以到時候自己修改一下正則表達式,這樣下下來的圖片也更高清的多摆马。
分析了正則臼闻,下面要獲取所有的圖片該分析Chrome開發(fā)者面板的Post數(shù)據(jù),因為知乎默認(rèn)只顯示部分回答囤采,我們可以不斷往下拉述呐,直到看到這個:
點擊的時候注意觀察開發(fā)者面板:
簡直完美,傳遞的數(shù)據(jù):
method:next
params:{"url_token":37709992,"pagesize":10,"offset":30}
很眼熟蕉毯,url_token
就是問題后面那串?dāng)?shù)字:
https://www.zhihu.com/question/37709992
pagesize
是固定的10乓搬,最后一個offset
偏移量同樣很好理解,這里顯示10應(yīng)該說的就是默認(rèn)顯示的10個答案代虾,后面還查看到如下數(shù)據(jù):
method:next
params:{"url_token":37709992,"pagesize":10,"offset":20}
method:next
params:{"url_token":37709992,"pagesize":10,"offset":30}
也就是說我們在瀏覽器上每翻過10個答案瀏覽器就會向服務(wù)器發(fā)送Post請求在加載十個答案进肯,恩差不多可以開始寫代碼了。
合
模擬登陸之后的操作是找到Post的真實地址模擬瀏覽器向服務(wù)器發(fā)送請求:
url = 'https://www.zhihu.com/node/QuestionAnswerListV2'
header = {
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36',
'Referer': 'https://www.zhihu.com/question/37709992',
'Origin': 'https://www.zhihu.com',
'Accept-Encoding': 'gzip, deflate, br',
}
data = {
'method': 'next',
'params': '{"url_token":' + str(37709992) + ',"pagesize": "10",' + \
'"offset":' + str(offset) + "}",
'_xsrf': getxsrf(),
}
注意
發(fā)送Post請求時候請加上'_xsrf': getxsrf()
這一行棉磨,否則的話返回的只會是404 Forbidden
江掩,應(yīng)該是做了防偽登陸的緣故
然后是寫正則,這里發(fā)現(xiàn)圖片都是被包含在這里面:
<div class="zm-editable-content clearfix">
...
...
</div>
所以先匹配到這一大串內(nèi)容:
pattern = re.compile('<a class="author-link".*?<span title=.*?<div class="zh-summary.*?' +
'<div class="zm-editable-content.*?>(.*?)</div>', re.S)
然后在匹配data-actualsrc
里面的圖片鏈接:
pattern = re.compile('data-actualsrc="(.*?)">', re.S)
還有一點要注意的是我們請求之后返回來的是json格式的數(shù)據(jù),所以這里還要用到j(luò)son模塊:
question = session.post(url, headers=header, data=data)
dic = json.loads(question.content.decode('ISO-8859-1'))
li = dic['msg'][0]
然后對其進行解析:
# 這里使用的是第一個正則表達式
items = re.findall(pattern, li)
# 接下來
items = re.findall(pattern, li)
# 存儲圖片鏈接
imagesurl = []
pattern = re.compile('data-actualsrc="(.*?)">', re.S)
for item in items:
urls = re.findall(pattern, item)
imagesurl.extend(urls)
執(zhí)行下載操作:
# 存放圖片的地址
PWD = "D:/work/python/zhihu/"
for url in imagesurl:
myurl = url
filename = PWD + str(count) + '.jpg'
if os.path.isfile(filename):
print("文件存在:", filename)
count += 1
continue
else:
# 執(zhí)行下載操作的方法
downpic(filename, myurl)
count += 1
photoNum += 1
print("一共下載了{(lán)0} 張照片".format(photoNum))
if not os.path.exists(PWD):
os.makedirs(PWD)
# 遞歸調(diào)用
change(offset, count, photoNum)
# downpic方法源碼
def downpic(filename, url):
print("正在下載 " + url)
try:
r = requests.get(url, stream=True)
with open(filename, 'wb') as fd:
for chunk in r.iter_content():
fd.write(chunk)
except Exception as e:
print("下載失敗了", e)
運行結(jié)果
這只是一部分环形,我之前下了四五百張還在下~當(dāng)然這是后話策泣,感覺現(xiàn)在寫的東西都很簡單,希望下一次能寫出難一點的東西出來抬吟。
這里正則部分參考了這里:
最后是源碼
源碼中注釋部分只能下載前十個答案里包含的圖片的方法萨咕,還有一些想法未完成,本來是想打印一下正在下載哪個答主的回答火本,然后把圖片分別保存到相應(yīng)的單獨文件夾危队,實現(xiàn)起來有點麻煩就沒去搞,僅供參考钙畔。
親測如果需要下載另一個問題的答案茫陆,只需要在:
data = {
'method': 'next',
'params': '{"url_token":' + str(37709992) + ',"pagesize": "10",' + \
'"offset":' + str(offset) + "}",
'_xsrf': getxsrf(),
}
更換那串?dāng)?shù)字就行,就好比這樣的形式:
但是這種形式的把數(shù)字換上去不起效:
這個好像是知乎熱門問答的鏈接形式擎析,暫時沒有深究
最后盅弛,覺得寫得不錯的可以關(guān)注一下我的公眾號呀~蟹蟹