該部落格首發於 www.litreily.top
其實,新浪微博使用者圖片爬蟲是我學習python
以來寫的第一個爬蟲,只不過當時懶,後來爬完Lofter
後覺得有必要總結一下,所以就有了第一篇爬蟲部落格。現在暫時閒下來了,準備把新浪的這個也補上。
言歸正傳,既然選擇爬新浪微博,那當然是有需求的,這也是學習的主要動力之一,沒錯,就是美圖。sina
使用者多數微博都是包含圖片的,而且是組圖居多,單個圖片的較少。
為了避免侵權,本文以本人微博litreily為例說明整個爬取過程,雖然圖片較少,質量較低,但爬取方案是絕對ok的,使用時只要換個使用者ID就可以了。
分析sina站點
獲取使用者ID
在爬取前,我們需要知道的是每個使用者都有一個使用者名稱,而一個使用者名稱又對應一個唯一的整型數字ID,類似於學生的學號,本人的是2657006573
。至於怎麼根據使用者名稱去獲取ID,有以下兩種方法:
- 進入待爬取使用者主頁,在瀏覽器網址欄中即可看到一串資料,那就是使用者ID
Ctrl-U
檢視待爬取使用者的原始碼,搜尋"uid
,注意是雙引號
其實是可以在已知使用者名稱的情況下通過爬蟲自動獲取到uid
的,但是我當時初學python
,並沒有考慮充分,所以後面的原始碼是以使用者ID作為輸入引數的。
圖片儲存引數解析
使用者所有的圖片都被存放至這樣的路徑下,真的是所有圖片哦!!!
https://weibo.cn/{uid}/profile?filter={filter_type}&page={page_num}
# example
https://weibo.cn/2657006573/profile?filter=0&page=1
uid: 2657006573
filter_type: 0
page_num: 1
複製程式碼
注意,是weibo.cn
而不是weibo.com
,至於我是怎麼找到這個頁面的,說實話,我也忘了。。。
連結中包含3個引數,uid
, filter_mode
以及 page_num
。其中,uid
就是前面提及的使用者ID,page_num
也很好理解,就是分頁的當前頁數,從1開始增加,那麼,這個filter_mode
是什麼呢?
不著急,我們先來看看頁面↓
可以看到,濾波型別filter_mode
指的就是篩選條件,一共三個:
- filter=0 全部微博(包含純文字微博,轉載微博)
- filter=1 原創微博(包含純文字微博)
- filter=2 圖片微博(必須含有圖片,包含轉載)
我通常會選擇原創,因為我並不希望爬取結果中包含轉載微博中的圖片。當然,大家依照自己的需要選擇即可。
圖鏈解析
好了,引數來源都知道了,我們回過頭看看這個網頁。頁面是不是感覺就是個空架子?毫無css痕跡,沒關係,新浪本來就沒打算把這個頁面主動呈現給使用者。但對於爬蟲而言,這卻是極好的,為什麼這麼說?原因如下:
- 圖片齊全,沒有遺漏,就是個視覺化的資料庫
- 樣式少,頁面簡單,省流量,爬取快
- 靜態網頁,分頁儲存,所見即所得
- 原始碼包含了所有微博的首圖和組圖連結
這樣的網頁用來練手再合適不過。但要注意的是上面第4點,什麼是首圖和組圖連結呢,很好理解。每篇部落格可能包含多張圖片,那就是組圖,但該頁面只顯示部落格的第一張圖片,即所謂的首圖,組圖連結指向的是儲存著該組圖所有圖片的網址。
由於本人微博沒組圖,所以此處以劉亦菲微博為例,說明單圖及組圖的圖鏈格式
圖中的上面一篇微博只有一張圖片,可以輕易獲取到原圖連結,注意是原圖,因為我們在頁面能看到的是縮圖,但要爬取的當然是原圖啦。
圖中下面的微博包含組圖,在圖片右側的Chrome
開發工具可以看到組圖連結。
https://weibo.cn/mblog/picAll/FCQefgeAr?rl=2
開啟組圖連結,可以看到圖片如下圖所示:
可以看到縮圖連結以及原圖連結,然後我們點選原圖看一下。
可以發現,彈出頁面的連結與上圖顯示的不同,但與上圖中的縮圖連結極為相似。它們分別是:
- 縮圖:http://ww1.sinaimg.cn/thumb180/c260f7ably1fn4vd7ix0qj20rs1aj1kx.jpg
- 原圖: http://wx1.sinaimg.cn/large/c260f7ably1fn4vd7ix0qj20rs1aj1kx.jpg
可以看出,只是一個thumb180
和large
的區別。既然發現了規律,那就好辦多了,我們只要知道縮圖的網址,就可以將域名後的第一級子域名替換成large
就可以了,而不用獲取原圖連結再跳轉一次。
而且,多次嘗試可以發現組圖連結及縮圖連結滿足正規表示式:
# 1. 組圖連結:
imglist_reg = r'href="(https://weibo.cn/mblog/picAll/.{9}\?rl=2)"'
# 2. 縮圖
img_reg = r'src="(http://w.{2}\.sinaimg.cn/(.{6,8})/.{32,33}.(jpg|gif))"'
複製程式碼
到此,新浪微博的解析過程就結束了,圖鏈的格式以及獲取方式也都清楚了。下面就可以設計方案進行爬取了。
確定爬取方案
根據解析結果,很容易制定出以下爬取方案:
- 給定微博使用者名稱
litreily
- 進入待爬取使用者主頁,即可從網址中獲取
uid: 2657006573
- 獲取本人登入微博後的
cookies
(請求報文需要用到cookies
) - 逐一爬取 https://weibo.cn/2657006573/profile?filter=0&page={1,2,3,...}
- 解析每一頁的原始碼,獲取單圖連結及組圖連結,
- 單圖:直接獲取該圖縮圖連結;
- 組圖:爬取組圖連結,迴圈獲取組圖頁面所有圖片的縮圖連結
- 迴圈將第5步獲取到的圖鏈替換為原圖連結,並下載至本地
- 重複第4-6步,直至沒有圖片
獲取cookies
針對以上方案,其中有幾個重點內容,其一就是cookies
的獲取,我暫時還沒學怎麼自動獲取cookies
,所以目前是登入微博後手動獲取的。
下載網頁
下載網頁用的是python3
自帶的urllib
庫,當時沒學requests
,以後可能也很少用urllib
了。
def _get_html(url, headers):
try:
req = urllib.request.Request(url, headers = headers)
page = urllib.request.urlopen(req)
html = page.read().decode('UTF-8')
except Exception as e:
print("get %s failed" % url)
return None
return html
複製程式碼
獲取儲存路徑
由於我是在win10
下編寫的程式碼,但是個人比較喜歡用bash
,所以圖片的儲存路徑有以下兩種格式,_get_path
函式會自動判斷當前作業系統的型別,然後選擇相應的路徑。
def _get_path(uid):
path = {
'Windows': 'D:/litreily/Pictures/python/sina/' + uid,
'Linux': '/mnt/d/litreily/Pictures/python/sina/' + uid
}.get(platform.system())
if not os.path.isdir(path):
os.makedirs(path)
return path
複製程式碼
幸好windows
是相容linux
系統的斜槓符號的,不然程式中的相對路徑替換還挺麻煩。
下載圖片
由於選用的urllib
庫,所以下載圖片就使用urllib.request.urlretrieve
了
# image url of one page is saved in imgurls
for img in imgurls:
imgurl = img[0].replace(img[1], 'large')
num_imgs += 1
try:
urllib.request.urlretrieve(imgurl, '{}/{}.{}'.format(path, num_imgs, img[2]))
# display the raw url of images
print('\t%d\t%s' % (num_imgs, imgurl))
except Exception as e:
print(str(e))
print('\t%d\t%s failed' % (num_imgs, imgurl))
複製程式碼
原始碼
其它細節詳見原始碼
#!/usr/bin/python3
# -*- coding:utf-8 -*-
# author: litreily
# date: 2018.02.05
"""Capture pictures from sina-weibo with user_id."""
import re
import os
import platform
import urllib
import urllib.request
from bs4 import BeautifulSoup
def _get_path(uid):
path = {
'Windows': 'D:/litreily/Pictures/python/sina/' + uid,
'Linux': '/mnt/d/litreily/Pictures/python/sina/' + uid
}.get(platform.system())
if not os.path.isdir(path):
os.makedirs(path)
return path
def _get_html(url, headers):
try:
req = urllib.request.Request(url, headers = headers)
page = urllib.request.urlopen(req)
html = page.read().decode('UTF-8')
except Exception as e:
print("get %s failed" % url)
return None
return html
def _capture_images(uid, headers, path):
filter_mode = 1 # 0-all 1-original 2-pictures
num_pages = 1
num_blogs = 0
num_imgs = 0
# regular expression of imgList and img
imglist_reg = r'href="(https://weibo.cn/mblog/picAll/.{9}\?rl=2)"'
imglist_pattern = re.compile(imglist_reg)
img_reg = r'src="(http://w.{2}\.sinaimg.cn/(.{6,8})/.{32,33}.(jpg|gif))"'
img_pattern = re.compile(img_reg)
print('start capture picture of uid:' + uid)
while True:
url = 'https://weibo.cn/%s/profile?filter=%s&page=%d' % (uid, filter_mode, num_pages)
# 1. get html of each page url
html = _get_html(url, headers)
# 2. parse the html and find all the imgList Url of each page
soup = BeautifulSoup(html, "html.parser")
# <div class="c" id="M_G4gb5pY8t"><div>
blogs = soup.body.find_all(attrs={'id':re.compile(r'^M_')}, recursive=False)
num_blogs += len(blogs)
imgurls = []
for blog in blogs:
blog = str(blog)
imglist_url = imglist_pattern.findall(blog)
if not imglist_url:
# 2.1 get img-url from blog that have only one pic
imgurls += img_pattern.findall(blog)
else:
# 2.2 get img-urls from blog that have group pics
html = _get_html(imglist_url[0], headers)
imgurls += img_pattern.findall(html)
if not imgurls:
print('capture complete!')
print('captured pages:%d, blogs:%d, imgs:%d' % (num_pages, num_blogs, num_imgs))
print('directory:' + path)
break
# 3. download all the imgs from each imgList
print('PAGE %d with %d images' % (num_pages, len(imgurls)))
for img in imgurls:
imgurl = img[0].replace(img[1], 'large')
num_imgs += 1
try:
urllib.request.urlretrieve(imgurl, '{}/{}.{}'.format(path, num_imgs, img[2]))
# display the raw url of images
print('\t%d\t%s' % (num_imgs, imgurl))
except Exception as e:
print(str(e))
print('\t%d\t%s failed' % (num_imgs, imgurl))
num_pages += 1
print('')
def main():
# uids = ['2657006573','2173752092','3261134763','2174219060']
uid = '2657006573'
path = _get_path(uid)
# cookie is form the above url->network->request headers
cookies = ''
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36',
'Cookie': cookies}
# capture imgs from sina
_capture_images(uid, headers, path)
if __name__ == '__main__':
main()
複製程式碼
使用時記得修改main
函式中的cookies
和uid
!