1. 程式人生 > >人生苦短,我用Python--分分鐘下載知乎美圖給你看

人生苦短,我用Python--分分鐘下載知乎美圖給你看

上次說了要爬知乎的圖片,於是花了一下午的時間去完成這件事,發現暫時接觸到的爬蟲總是逃脫不了一個規律:

  • 模擬登陸
  • 獲取真實網頁HTML原始碼
  • 解析獲取到的網頁原始碼
  • 獲取想要的資源(下載到某個資料夾或者輸出到表格中整合起來)

也許和我說的有一些出入,應該是剛學這個東西的原因,接下來還想研究一下多執行緒爬蟲、新增代理、爬取海量資料並整合成圖示形式,先把能做的做了。

因為是在上一次的基礎上進行的,所以沒有看上一篇文章的可以先看一下,這裡用到的工具跟之前一樣:

  • 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(): # 通過檢視使用者個人資訊來判斷是否已經登入 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,但是我學的是Python3.x所以去掉了一些我沒用過的模組,也就是說我改進了後的程式碼是適用於Python3.x的。

下面就是準備獲取圖片了,先找一個目標,最近有一個問題很火:

還記得我列出來的步驟麼,模擬登陸之後是獲取真實的網頁原始碼,什麼叫真實的,這個問題問得好,你沒發現知乎很喜歡用動態載入技術麼,也就是說,你看到的只是表象,這裡也一樣。

插播一下:這篇文章首發http://blog.csdn.net/wei_smile,同步發表在簡書跟個人部落格,無恥的code xiu網站經常盜用我的文章不署名,盜版可恥,抄襲下流~~

問題介面

來,我們先點贊數最高的妹子上傳的圖片:

Beauty

咳咳咳,好像跑偏了,我們的目標是星辰大海,正確的做法是滑鼠右鍵檢視網頁原始碼:

網頁原始碼

是不是看到了很多圖片連結,當然我們要找.jpg、.jpeg、.png字尾的:

<img data-rawwidth="1632" data-rawheight="2040" src="//zhstatic.zhihu.com/assets/zhihu/ztext/whitedot.jpg" class="origin_image zh-lightbox-thumb lazy" width="1632" data-original="https://pic2.zhimg.com/b6274542f3785c27ab4a38d4db906efd_r.jpg" data-actualsrc="https://pic2.zhimg.com/b6274542f3785c27ab4a38d4db906efd_b.jpg">

這裡有兩個:data-originaldata-actualsrc,實際檢視的圖片是data-original的圖片比data-actualsrc的大,下載下來也是如此,但是因為是使用正則去匹配規則,而data-original有多項,上面程式碼只是貼出來的一部分,實際匹配的結果類似這樣:

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">

這是在同一段網頁原始碼測試下的結果,匹配後一種會得到多個相同的url地址,解析起來也更麻煩,這也跟正則寫的簡單有關係,有興趣的可以到時候自己修改一下正則表示式,這樣下下來的圖片也更高清的多。

分析了正則,下面要獲取所有的圖片該分析Chrome開發者面板的Post資料,因為知乎預設只顯示部分回答,我們可以不斷往下拉,直到看到這個:

更多

點選的時候注意觀察開發者面板:

enter image description here

簡直完美,傳遞的資料:

method:next
params:{"url_token":37709992,"pagesize":10,"offset":30}

很眼熟,url_token就是問題後面那串數字:

https://www.zhihu.com/question/37709992

pagesize是固定的10,最後一個offset偏移量同樣很好理解,這裡顯示10應該說的就是預設顯示的10個答案,後面還檢視到如下資料:

method:next
params:{"url_token":37709992,"pagesize":10,"offset":20}
method:next
params:{"url_token":37709992,"pagesize":10,"offset":30}

也就是說我們在瀏覽器上每翻過10個答案瀏覽器就會向伺服器傳送Post請求在載入十個答案,恩差不多可以開始寫程式碼了。

模擬登陸之後的操作是找到Post的真實地址模擬瀏覽器向伺服器傳送請求:

    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(),

    }

注意

傳送Post請求時候請加上'_xsrf': getxsrf()這一行,否則的話返回的只會是404 Forbidden,應該是做了防偽登陸的緣故

然後是寫正則,這裡發現圖片都是被包含在這裡面:

<div class="zm-editable-content clearfix">
...
...
</div>

所以先匹配到這一大串內容:

        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格式的資料,所以這裡還要用到json模組:

 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)

執行下載操作:

# 存放圖片的地址
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:
            # 執行下載操作的方法
                downpic(filename, myurl)
                count += 1
                photoNum += 1
            print("一共下載了{0} 張照片".format(photoNum))
            if not os.path.exists(PWD):
                os.makedirs(PWD)
                # 遞迴呼叫
        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)

執行結果

執行結果

enter image description here

這只是一部分,我之前下了四五百張還在下~當然這是後話,感覺現在寫的東西都很簡單,希望下一次能寫出難一點的東西出來。

這裡正則部分參考了這裡:

最後是原始碼

原始碼中註釋部分只能下載前十個答案裡包含的圖片的方法,還有一些想法未完成,本來是想列印一下正在下載哪個答主的回答,然後把圖片分別儲存到相應的單獨資料夾,實現起來有點麻煩就沒去搞,僅供參考。

親測如果需要下載另一個問題的答案,只需要在:

 data = {
        'method': 'next',
        'params': '{"url_token":' + str(37709992) + ',"pagesize": "10",' + \
                  '"offset":' + str(offset) + "}",
        '_xsrf': getxsrf(),

    }

更換那串數字就行,就好比這樣的形式:

但是這種形式的把數字換上去不起效:

這個好像是知乎熱門問答的連結形式,暫時沒有深究

這裡寫圖片描述