人生苦短,我用Python--一起來爬知乎娘

上次爬了教務(wù)處的成績,接下來想去爬一爬知乎娘,好像大家都很喜歡爬知乎娘,GitHub上貌似已經(jīng)有人把獲取各種知乎數(shù)據(jù)的操作封裝好了:zhihu-python ,但是良辰表示還是想自己試一試,我就爬點簡單的,難度大的我也不會,我瞄準的是我自己關(guān)注的人的數(shù)據(jù),差不多就這些東西:

關(guān)注者 / 提問 / 回答 / 贊同

那么下面開始分析,要拿到這個數(shù)據(jù)肯定是要先登陸的,那么先找到登陸界面:
登陸界面

這算是比較正常的界面,不知道為什么有的時候會出現(xiàn)新的驗證碼形式:
輸入倒立的文字
輸入倒立的文字
這種形式登陸的話我看傳遞的表單數(shù)據(jù)是你鼠標點擊的坐標,這種情況暫時不知道怎么辦,留給高手,我們分析上面那種,知乎登陸分為手機號登陸或者郵箱登陸,先試試郵箱登陸,查看Post數(shù)據(jù):
郵箱登陸
然后是手機號登陸的:
手機號登陸

現(xiàn)在理一下思路,我們要登錄知乎要傳遞的數(shù)據(jù)如下:

  • _xsrf()用于防偽登陸
  • password- captcha 驗證碼
  • phone_num/email 不同登陸方式傳遞的東西不同

phone_num/email以及passsword都需要自己輸入,這個好辦,我們需要解決如何獲取 _xsrf和captcha 的問題,先解決 _xsrf,這個更簡單一點,我們在知乎登陸頁面 右鍵查看網(wǎng)頁源代碼,直接搜_xsrf:

<div class="view view-signin" data-za-module="SignInForm">
<form><input type="hidden" name="_xsrf" 
value="cf1ee28f15cea5dba3243a1c31a1b284"/><div class="group-inputs">

我們要做的就是解析出這個元素,直接上代碼:

# 構(gòu)造 Request headers
# 登陸的url地址
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']

下面獲取驗證碼, 我發(fā)現(xiàn)無論是使用哪一種登陸方式,Chrome的開發(fā)者面板都會有這么一行:
驗證碼請求

當我把這個Request URL輸入瀏覽器中就會轉(zhuǎn)到這個驗證碼圖片的界面,說明這個驗證碼是我們加載這個頁面時候,瀏覽器向服務(wù)器發(fā)出請求然后下載下來的,那么現(xiàn)在我們有了URL地址:
URL地址
這還是一個GET請求,這里注意這個傳遞的r參數(shù)是會不斷變化的,這是一個當前Unix的時間戳,知道了這些可以著手獲取驗證碼然后讓用戶自己輸入:
try:
    from PIL import Image
except:
    pass
# 獲取驗證碼
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

這里獲取驗證碼的代碼來自GitHub上的fuck-login項目,在此表示感謝,接下里就是寫一個方法判斷用戶是使用了哪一種登陸方式,然后傳遞相應(yīng)的數(shù)據(jù):

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'])

# 這部分代碼同樣來自[**fuck-login**](https://github.com/xchaoinfo/fuck-login)項目,我偷了很多懶,囧

這個時候判斷是否登陸成功可以隨便查看一個頁面發(fā)送GET請求看是否返回200,這里就不多說,接下來才是關(guān)鍵,為了獲取我關(guān)注的人的信息,先打開開發(fā)者面板,這個地方打鉤:
記得打鉤

因為網(wǎng)頁有時候會跳轉(zhuǎn),這里打鉤之后新跳轉(zhuǎn)的頁面的信息就不會覆蓋之前接受到的信息,然后找到我關(guān)注的人,URL地址是:

https://www.zhihu.com/people/GitSmile/followees

最開始我走了彎路,我以為直接在這個頁面解析出我關(guān)注的人的信息就行,所以一開始我是這么做的,查看網(wǎng)頁源代碼,這里隨便找一個我關(guān)注的人的信息:

<a title="死者代言人"
data-hovercard="p$t$forensic"
class="zm-item-link-avatar"
href="/people/forensic">
![](https://pic1.zhimg.com/be39d110759e68f389b7d2934d7353bc_m.jpg)
</a>
<div class="zm-list-content-medium">
<h2 class="zm-list-content-title"><a data-hovercard="p$t$forensic"  class="zg-link author-link" title="死者代言人"
>死者代言人</a><span class="icon icon-badge-best_answerer icon-badge" data-tooltip="s$b$優(yōu)秀回答者"></span></h2>

<div class="ellipsis">
<span class="badge-summary">優(yōu)秀回答者</span>
<span class="bio">不養(yǎng)喵的愛喵法醫(yī)。</span>
</div>
<div class="details zg-gray">
<a target="_blank" href="/people/forensic/followers" class="zg-link-gray-normal">35968 關(guān)注者</a>
/
<a target="_blank" href="/people/forensic/asks" class="zg-link-gray-normal">2 提問</a>
/
<a target="_blank" href="/people/forensic/answers" class="zg-link-gray-normal">305 回答</a>
/
<a target="_blank" href="/people/forensic" class="zg-link-gray-normal">51278 贊同</a>
</div>

然后在對比另一條:

<a title="陳亦飄"
data-hovercard="p$t$chen-yi-piao"
class="zm-item-link-avatar"
href="/people/chen-yi-piao">
![](https://pic2.zhimg.com/d495751efbf837b1b5b08571add7df3d_m.jpg)
</a>
<div class="zm-list-content-medium">
<h2 class="zm-list-content-title"><a data-hovercard="p$t$chen-yi-piao"  class="zg-link author-link" title="陳亦飄"
>陳亦飄</a></h2>

<div class="ellipsis">

<span class="bio">音樂和電影是我的愛與慈悲</span>
</div>
<div class="details zg-gray">
<a target="_blank" href="/people/chen-yi-piao/followers" class="zg-link-gray-normal">74469 關(guān)注者</a>
/
<a target="_blank" href="/people/chen-yi-piao/asks" class="zg-link-gray-normal">0 提問</a>
/
<a target="_blank" href="/people/chen-yi-piao/answers" class="zg-link-gray-normal">80 回答</a>
/
<a target="_blank" href="/people/chen-yi-piao" class="zg-link-gray-normal">315971 贊同</a>
</div>

要分解出這些元素,只要找出他們的共同點,當時機智如我一眼就發(fā)現(xiàn)每一個我關(guān)注的人的名字都有這么一行元素:

class="zm-item-link-avatar"

而且都包含在一個<a></a>標簽里面,這就好辦了,使用BeautifulSoup先分解出每一個我關(guān)注的人的名字:

def getdetial():
    followees_url = 'https://www.zhihu.com/people/GitSmile/followees'
    followees_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',
        'Referer': 'https://www.zhihu.com/people/GitSmile/about',
        'Upgrade-Insecure-Requests': '1',
        'Accept-Encoding': 'gzip, deflate, sdch, br'
    }

    myfollowees = session.get(followees_url, headers=followees_headers)
    mysoup = BeautifulSoup(myfollowees.content, 'html.parser')
    print(mysoup.find('span', attrs={'class': 'zm-profile-section-name'}).text)

然后繼續(xù)觀察,突破點依然在這個<a></a>標簽,拿"陳亦飄"的信息做個例子,看官們看這里面是不是有個href="/people/chen-yi-piao,然后看一下她的相關(guān)信息:

<a target="_blank" href="/people/chen-yi-piao/followers" class="zg-link-gray-normal">74469 關(guān)注者</a>
/
<a target="_blank" href="/people/chen-yi-piao/asks" class="zg-link-gray-normal">0 提問</a>
/
<a target="_blank" href="/people/chen-yi-piao/answers" class="zg-link-gray-normal">80 回答</a>
/
<a target="_blank" href="/people/chen-yi-piao" class="zg-link-gray-normal">315971 贊同</a>

發(fā)現(xiàn)沒有,每一個信息里面的href元素都是我們最開始看到的/people/chen-yi-piao元素加上一些字符串組成的,那么我就可以在這上面做點文章:

獲取詳細信息
def getdetial():
    followees_url = 'https://www.zhihu.com/people/GitSmile/followees'
    followees_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',
        'Referer': 'https://www.zhihu.com/people/GitSmile/about',
        'Upgrade-Insecure-Requests': '1',
        'Accept-Encoding': 'gzip, deflate, sdch, br'
    }

    myfollowees = session.get(followees_url, headers=followees_headers)
    mysoup = BeautifulSoup(myfollowees.content, 'html.parser')
    print(mysoup.find('span', attrs={'class': 'zm-profile-section-name'}).text)
    for result in mysoup.findAll('a', attrs={'class': 'zm-item-link-avatar'}):
        print(result.get('title'))
        # 解析出href元素信息
        href = str(result.get('href'))
        # 關(guān)注者
        print(mysoup.find('a', attrs={'href': href + '/followers'}).text)
        # 提問
        print(mysoup.find('a', attrs={'href': href + '/asks'}).text)
        # 回答
        print(mysoup.find('a', attrs={'href': href + '/answers'}).text)
        # 贊同
        print(mysoup.find('a', attrs={'href': href, 'class': 'zg-link-gray-normal'}).text + '\n')

上面的代碼應(yīng)該很簡單了,看一看就能看懂,然后就我信心滿滿地在PyCharm上輸出的時候,發(fā)現(xiàn)來來回回只輸出20條信息,也就是說,我關(guān)注了26人,但是控制臺只輸出了20個人的信息,然后我就上網(wǎng)查,發(fā)現(xiàn)不止一個人有我這樣的疑問,當然也多虧了前人踩坑,網(wǎng)上給出來的答案是知乎獲取關(guān)注的人的時候使用了AJAX技術(shù),也就是動態(tài)加載,但是這一部分代碼不會再網(wǎng)頁Html代碼中顯示出來,所以為了獲取其他關(guān)注的人的信息我這里要另辟蹊徑。

然后我翻看我關(guān)注的人的信息的時候,在開發(fā)者面板抓到這么一條POST信息:

enter image description here

這條POST之后下面刷出來的圖片是我關(guān)注的人的頭像并且這些頭像在我之前看到的20條數(shù)據(jù)里面是沒有的,加上我總共就關(guān)注了27個人,所以我有理由相信這個Post就是瀏覽器向服務(wù)器發(fā)送請求的Post,看一下Post的信息:

enter image description here

一個偏移量(offset),一個哈希值(hash_id)外加一個"order_by":"created"的鍵值對,這里偏移量很好理解,這個"hash_id"據(jù)我多次登陸發(fā)現(xiàn)是一個不變的值,或者說每一臺電腦或許精確一點每一個瀏覽器都會有這么一個給定的值,照抄,那么改進后的代碼如下:

# 獲取所有關(guān)注的人的信息
def getallview():
    nums = 27  # 這個是我關(guān)注的人數(shù)
    followees_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',
        'Referer': 'https://www.zhihu.com/people/GitSmile/followees',
        'Origin': 'https://www.zhihu.com',
        'Accept-Encoding': 'gzip, deflate, br',
        'CG - Sid': '57226ad5 - 793b - 4a9d - 9791 - 2a9a17e682ef',
        'Accept': '* / *'

    }
    # 序號
    count = 0
    for index in range(0, nums):
        fo_url = 'https://www.zhihu.com/node/ProfileFolloweesListV2'
        m_data = {
            'method': 'next',
            'params': '{"offset":' + str(
                index) + ',"order_by":"created","hash_id":"de2cb64bc1afe59cf8a6e456ee5eaebc"}',
            '_xsrf': str(getxsrf())
        }
        result = session.post(fo_url, data=m_data, headers=followees_headers)
        dic = json.loads(result.content.decode('utf-8'))
        li = dic['msg'][0]
        mysoup = BeautifulSoup(li, 'html.parser')
        for result in mysoup.findAll('a', attrs={'class': 'zm-item-link-avatar'}):
            print(index + 1)
            print(result.get('title'))
            href = str(result.get('href'))
            print(mysoup.find('a', attrs={'href': href + '/followers'}).text)
            print(mysoup.find('a', attrs={'href': href + '/asks'}).text)
            print(mysoup.find('a', attrs={'href': href + '/answers'}).text)
            print(mysoup.find('a', attrs={'href': href, 'class': 'zg-link-gray-normal'}).text + '\n')
            count += 1
    print('一共關(guān)注了 %d人' % count)

放上程序的入口:

if __name__ == '__main__':

    if isLogin():
        print('您已經(jīng)登錄')
    else:
        account = input('請輸入你的用戶名\n>  ')
        secret = input("請輸入你的密碼\n>  ")
        login(secret, account)
    getallview()

看一下實際效果:

結(jié)果

寫在結(jié)尾

雖然簡單,但是提供了一些初學(xué)者的思路,下一次準備爬一些知乎上的圖片,好像很多人都熱衷于這種事,嘻嘻,荊軻刺秦王。

源碼

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,324評論 25 708
  • Scrapy爬蟲教程一 Windows下安裝Scrapy的方式和問題總結(jié) Scrapy爬蟲教程二 淺析最煩人的反爬...
    熊熊熊孩子閱讀 4,590評論 6 18
  • 模擬登錄知乎 這幾天在研究模擬登錄, 以知乎 - 與世界分享你的知識、經(jīng)驗和見解為例。實現(xiàn)過程遇到不少疑問,借鑒了...
    sunhaiyu閱讀 27,372評論 51 115
  • 感冒了 早上開會講了關(guān)于三問價格。 今天隊友們共25個定金,我不在其中。 要了5個還差9個資源,做了將近六分鐘平板...
    Fineyoga瑾璟閱讀 230評論 0 0
  • 目光流轉(zhuǎn)疑尋人 步履輕緩繞圓繞 遠望西山哀憐色 垂頭凌落青絲嫵 不惹余暉分外柔 竊以偶得連己心 去去舍舍恰別離 千...
    南臣閱讀 302評論 0 4

友情鏈接更多精彩內(nèi)容