iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 9
0
Software Development

[行銷也要自動化] 用 Python Selenium + NodeJS + Amazon EC2 打造簡易關鍵字搜尋報表應用!系列 第 9

Day8:Python requests + bs4 實作模擬登入網站並爬取資料|Kearch 1.0 爬蟲關鍵字報表工具

上一篇我們用scrapy來爬Y Combinator Blog,它的結構不算太複雜,而且無須登入。
但如果今天需要爬取需要登入的網站資料該怎麼做呢?我們可以用python requests來實作。

本文感謝大數學堂的啟發,加上參考國外另一位Blogger分享的解法、簡化後整理而成

為什麼加了headers和帳密還是無法成功爬到我要的資訊?

一般可能會透過加headers的方法來偽裝是一般使用者對目標網站發出requests,以防被網站block;如果遇到要登入的網站則是運用POST方法把自己的帳密傳送到login的url。
不過有些網站(像我們今天的範例 Anewstip),它在登入表單上有一個隱藏欄位叫"csrfmiddlewaretoken"(按F12對照一下表單欄位會發現),每次登入前會自動產生不同的亂數,登入的時候會跟著一起傳送過去;換句話說如果爬蟲時沒辦法帶到csrfmiddlewaretoken的值,便無法成功登入

該如何解決csrfmiddlewaretoken隱藏值的問題?

遇到這種狀況,我們需要在登入同時把該頁面的csrfmiddlewaretoken value存起來,連同自己的帳密做POST request

python requests 登入解法開始

  1. 啟動jupyter notebook
    用jupyter的好處是可以記錄自己嘗試的過程,加上output也比較好閱讀。如果你有跟著前面的文章,就可以直接運行指令。他會幫你打開介面,你在上面new一個.ipynb,挑Python3的喔:(2和3語法不同,如果你挑2可能下面的code會error)
user@ubuntu:NodeJS/tutorial/views$ jupyter notebook

還沒安裝jupyter notebook? →看anaconda(含jupyter nb)安裝教學

  1. import套件,requests用來處理http請求、html及bs4用來篩選及處理爬到的資訊。
import requests
from lxml import html
from bs4 import BeautifulSoup as bs
  1. 先將登入資訊存在variable裡面方便等下用,LOGIN_URL是目標網站登入表單的位址,因為後面的code我們會需要match到表單各個input name。
USEREMAIL = 'yourname@example.com'
PASSWORD = '*******'
LOGIN_URL = 'https://anewstip.com/accounts/login/'
  1. 撰寫在登入頁面先拿取token的部分
    requests.session()是幫助我們把這一次的request都算在同一個session裡,這樣我們第二次對登入頁面發request時,csrfmiddlewaretoken value才不會又重新產生。
session_requests = requests.session()
  1. 接下來正式對剛剛存好的LOGIN_URL發出get request,用把result.text(也就是網頁的結構)存起來解析,使用xpath找到隱藏的csrfmiddlewaretoken值,先將它存起來。
result = session_requests.get(LOGIN_URL)
tree = html.fromstring(result.text)
authenticity_token = list(set(tree.xpath('//input[@name="csrfmiddlewaretoken"]/@value')))[0]
  1. 接著把完整的headers也先寫好,建議直接參考你的目標網站的request headers(按F12後切換到network panel;當你填好帳密按下登入的瞬間要注意看,網頁這時載入了什麼資料;通常你應該會看到一個hostname相同、是POST method,又帶有login或signin相關字眼的網址,那個很可能就是當你登入時觸發的。
    點開來看右邊會有request hearders和data format,如果看到你剛剛填的帳密就沒錯了。)

這邊要特別注意,記得仔細觀察自己的目標網站cookie的變化;這次我的目標網站它的cookie值包含csrfmiddlewaretoken,因此要讓它隨著每次登入置換

headers = {
    'Connection': 'keep-alive',
    'Content-Length': '103',
    'Cache-Control': 'max-age=0',
    'Origin': 'https://anewstip.com',
    'Upgrade-Insecure-Requests': '1',
    'Content-Type': 'application/x-www-form-urlencoded',
    'User-Agent': 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Mobile Safari/537.36',
    'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
    'Referer': LOGIN_URL,
    'Accept-Encoding': 'gzip, deflate, br',
    'Accept-Language': 'zh-TW,zh;q=0.9,en-US;q=0.8,en;q=0.7',
    'Cookie': '_ga=GA1.2.218269643.1513734952; _gid=GA1.2.1405785245.1513907212; csrftoken='+authenticity_token+'; sessionid=yvb8vq6m4katwmz76d0cnjubd29pdrdb; _gat=1'
}
  1. 準備好token和登入帳密
payload = {
    'email': USEREMAIL,
    'password': PASSWORD,
    'csrfmiddlewaretoken': authenticity_token
}

7. 以POST method登入
以剛剛啟動的session實例來發post request,才會在同一個session裡。後面記得帶上登入資訊及標頭參數

result = session_requests.post(LOGIN_URL, data = payload, headers = headers)

8. 以GET method爬取資料
如果你的網站只有一個頁面要爬,直接貼上就可以;但如果是需要換頁的,可以使用forLoop加頁碼參數。
若是動態載入的換頁,可以觀察network panel是否有規律的json檔名可以爬;再沒有就出動神器selenium吧(是我們後天的主題。)
注:通常如果使用loop連續對網站發出request,建議使用time套件設定sleep,降低IP被目標網站封鎖的風險。

result = session_requests.get(URL, headers = dict(referer = URL))
  1. 以bs4指定爬取物件
    把剛剛的result.text用BeautifulSoup做解析,並透過css selector找到你要的物件。
    這邊的範例是我想要一次獲取所有記者的profile link做二次處理。
soup = bs(result.text, 'html.parser')

for link in soup.select('.info-name a'):
    print('https://anewstip.com/'+link.get('href'))

  1. 換頁迴圈
    我這邊直接用迴圈來跑頁碼,只是要注意int和str間的轉換。
    注:不一定要用我這個方法,也可以續用bs4來抓頁面上Next page按鈕的href,置換到URL變數裡
...
def main():
    for i in range(1, 11):
        URL = 'https://anewstip.com/search/journalists/?q=privacy&page=' + str(i)
        ...

Python requests登入網站爬蟲解法總結

把上面全部combine起來:

import requests
from lxml import html
from bs4 import BeautifulSoup as bs

USEREMAIL = 'yourname@example.com'
PASSWORD = '*******'

LOGIN_URL = 'https://anewstip.com/accounts/login/'

def main():
    for i in range(1, 11):
        URL = 'https://anewstip.com/search/journalists/?q=privacy&page=' + str(i)
        session_requests = requests.session()

        result = session_requests.get(LOGIN_URL)
        tree = html.fromstring(result.text)
        authenticity_token = list(set(tree.xpath('//input[@name="csrfmiddlewaretoken"]/@value')))[0]

        headers = {
            'Connection': 'keep-alive',
            'Content-Length': '103',
            'Cache-Control': 'max-age=0',
            'Origin': 'https://anewstip.com',
            'Upgrade-Insecure-Requests': '1',
            'Content-Type': 'application/x-www-form-urlencoded',
            'User-Agent': 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Mobile Safari/537.36',
            'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
            'Referer': LOGIN_URL,
            'Accept-Encoding': 'gzip, deflate, br',
            'Accept-Language': 'zh-TW,zh;q=0.9,en-US;q=0.8,en;q=0.7',
            'Cookie': '_ga=GA1.2.218269643.1513734952; _gid=GA1.2.1405785245.1513907212; csrftoken='+authenticity_token+'; sessionid=yvb8vq6m4katwmz76d0cnjubd29pdrdb; _gat=1'
        }

        payload = {
            'email': USEREMAIL,
            'password': PASSWORD,
            'csrfmiddlewaretoken': authenticity_token
        }

        result = session_requests.post(LOGIN_URL, data = payload, headers = headers)

        result = session_requests.get(URL, headers = dict(referer = URL))
        soup = bs(result.text, 'html.parser')

        for link in soup.select('.info-name a'):
            print('https://anewstip.com/'+link.get('href'))

if __name__ == '__main__':
    main()
    

跑起來之後output你會得到每個記者的profile url:
(在jupyter上是直接可以點的url~儘管我也沒有要直接使用它XD 我是拿來後續再跑scrapy抓個別url的social link~)

https://anewstip.com//journalist/profile/bsan805668820/
https://anewstip.com//journalist/profile/bsan805844974/
https://anewstip.com//journalist/profile/bsan743221732/
https://anewstip.com//journalist/profile/bsan91445200/
https://anewstip.com//journalist/profile/bsan767309356/
https://anewstip.com//journalist/profile/bsan871832038/
https://anewstip.com//journalist/profile/bsan83712574/

再次要強烈建議自己試做一次,不要直接複製貼上,因為像獲取token值和headers就不一定是每個網站都相同。儘管也有參考其他人作法,但一定有需要自己研究的成分在der。

明天會介紹遇到ajax動態載入資料的網站的爬蟲破解法~
未來:愛用者本人還是要不斷地預告selenium及xvfb XD —— 當今天連json檔案都沒法在network panel看到時該怎麼hack?

happy requesting!


上一篇
Day7: 實作Python Scrapy 20行內爬取Y Combinator Blog所有文章|Kearch 1.0 爬蟲關鍵字報表工具
下一篇
Day9:ajax動態載入網頁爬蟲|Kearch 1.0 爬蟲關鍵字報表工具
系列文
[行銷也要自動化] 用 Python Selenium + NodeJS + Amazon EC2 打造簡易關鍵字搜尋報表應用!14

尚未有邦友留言

立即登入留言