前のレッスンでは、Webサイトのログイン検証とシミュレートされたログインの基本原則について学びました。ウェブサイトのログイン検証には2つの主要な実装があります。1つはセッション+ Cookieに基づくログイン検証、もう1つはJWTに基づくログイン検証です。このレッスンでは、2つの例を使用して、これら2つのログイン検証の分析と検証について説明します。ログインプロセスをシミュレートします。
準備オーケー
このレッスンを始める前に、次の準備が整っていることを確認してください。
- Python(できればバージョン3.6以上)をインストールし、Pythonプログラムを正常に実行できること。
- リクエストリクエストライブラリをインストールし、その基本的な使用方法を学びました。
- Seleniumライブラリーをインストールして、その基本的な使用法を学びました。
以下では、2つのケースを例として、シミュレートされたログインの実装についてそれぞれ説明します。
事例紹介
クロールするにはログインが必要な2つのWebサイトがあります。リンクはhttps://login2.scrape.cuiqingcai.com/とhttps://login3.scrape.cuiqingcai.com/です。前者はセッション+ Cookie認証に基づくWebサイトです。後者は、JWT認証に基づくWebサイトです。
まず最初のウェブサイトを見ると、開くと図のようなページが表示されます。
ユーザー名とパスワードがどちらもadminであるログインページに直接ジャンプし、入力後にログインします。
ログインに成功すると、図に示すように、おなじみの映画のWebサイトの表示ページが表示されます。
このWebサイトは、従来のMVCモデルに基づいて開発されているため、セッション+ Cookie認証にも適しています。
2番目のWebサイトが開かれた後、図に示すように、ログインページにもジャンプします。
ユーザー名とパスワードが同じです。adminと入力してログインしてください。
ログイン後、図に示すように、ホームページにジャンプし、書籍情報を表示します。
このページは、フロントエンドページとバックエンドページに分かれています。データの読み込みは、AjaxリクエストのバックエンドAPIインターフェースを介して取得されます。ログイン検証は、JWTに基づいています。同時に、各バックエンドAPIは、JWTが有効かどうか、および無効かどうかを検証しますデータは返されません。
ケースナンバーワン
次に、これら2つのケースを分析し、シミュレートされたログインを実装します。
ケース1の場合、ログインをシミュレートするには、ログインプロセスで発生したことを分析する必要があります。最初にhttps://login2.scrape.cuiqingcai.com/を開き、ログイン操作を実行して、ログインプロセス中に発生したことを確認します示されているように要求します。
ここで、彼がログインした瞬間に彼がPOSTリクエストを開始し、ターゲットURLがhttps://login2.scrape.cuiqingcai.com/loginであり、ログインデータがフォームの送信を通じて送信されたことがわかります。 、返されるステータスコードは302で、レスポンスヘッダーの場所フィールドはルートページです。レスポンスヘッダーには、Cookieの設定情報が含まれ、セッションIDが設定されます。
これから、シミュレートされたログインを実現するには、このリクエストをシミュレートするだけでよいことがわかります。ログインが完了したら、Responseによって設定されたCookieを取得し、Cookieを保存します。その後のリクエストには、Cookieを使用して通常どおりアクセスできます。
OK、次にコードで実装しましょう。
リクエストデフォルトでは、各リクエストは独立しており、相互に干渉しません。たとえば、最初にpostメソッドを呼び出してログインをシミュレートし、次にgetメソッドを呼び出してメインページをリクエストします。実際、これらは2つの完全に独立したリクエストです。最初の要求で取得したCookieを2番目の要求に渡すことはできないため、従来の順次呼び出しではログインをシミュレートする効果はありません。
まず、無効なコードを見てみましょう。
import requests
from urllib.parse import urljoin
BASE_URL = 'https://login2.scrape.cuiqingcai.com/'
LOGIN_URL = urljoin(BASE_URL, '/login')
INDEX_URL = urljoin(BASE_URL, '/page/1')
USERNAME = 'admin'
PASSWORD = 'admin'
response_login = requests.post(LOGIN_URL, data={
'username': USERNAME,
'password': PASSWORD
})
response_index = requests.get(INDEX_URL)
print('Response Status', response_index.status_code)
print('Response URL', response_index.url)
ここでは、最初にいくつかの基本的なURL、ユーザー名、パスワードを定義してから、リクエストを使用してログインURLをリクエストし、ログインをシミュレートしてから、ホームページをリクエストしてページコンテンツを取得しますが、データは正常に取得できますか?
リクエストはリダイレクトを自動的に処理できるため、最終的にレスポンスのURLを出力します。結果がINDEX_URLであれば、シミュレートされたログインが成功し、ホームページのコンテンツが正常にクロールされたことを証明します。ログインページに戻ると、シミュレートされたログインは失敗しました。
結果で確認してみましょう。結果は次のとおりです。
Response Status 200
Response URL https://login2.scrape.cuiqingcai.com/login?next=/page/1
ここでは、最終ページURLがログインページのURLであることがわかります。また、ページのソースコードは、応答のtext属性によっても確認できます。ソースコードコンテンツは、ログインページのソースコードコンテンツです。コンテンツが大きいため、ここには出力されません。それを比較してください。
つまり、この現象は、シミュレートされたログインを正常に完了できなかったことを示しています。これは、リクエストがpost、getなどのメソッドを直接呼び出すためです。各リクエストは独立したリクエストであり、これらのリンクを開くために新しいブラウザを開くことと同じです。このリクエストに対応するセッションは同じではないため、ここでは最初のセッションのログインをシミュレートします。これは2番目のセッションの状態には影響しないため、シミュレートされたログインは無効です。
それでは、どのようにして正しいシミュレートされたログインを実現できますか?
セッションID情報はCookieに保存されていることがわかっています。ログイン成功後、応答ヘッダーにset-cookieフィールドがあることがわかりました。実際、これはブラウザにCookieを生成させるためです。
CookieにはセッションID情報が含まれているため、後続のリクエストがこれらのCookieを運ぶ限り、サーバーはCookieのセッションID情報を通じて対応するセッションを見つけることができるため、サーバーはこれら2つのリクエストに同じセッションを使用します。また、初めてシミュレーションログインを完了したため、最初のシミュレーションログインが成功した後、ユーザーのログイン情報がセッションに記録されます。2回目にアクセスすると、同じセッションであるため、サーバーは現在のユーザーを知ることができます。ログインステータスの場合は、ログインページにジャンプする代わりに、正しい結果を返すことができます。
したがって、ここで重要なのは、2つの要求に対するCookieの配信です。したがって、最初のシミュレートされたログイン後にCookieを保存し、2番目のリクエストにこのCookieを追加して、コードを次のように書き換えることができます。
import requests
from urllib.parse import urljoin
BASE_URL = 'https://login2.scrape.cuiqingcai.com/'
LOGIN_URL = urljoin(BASE_URL, '/login')
INDEX_URL = urljoin(BASE_URL, '/page/1')
USERNAME = 'admin'
PASSWORD = 'admin'
response_login = requests.post(LOGIN_URL, data={
'username': USERNAME,
'password': PASSWORD
}, allow_redirects=False)
cookies = response_login.cookies
print('Cookies', cookies)
response_index = requests.get(INDEX_URL, cookies=cookies)
print('Response Status', response_index.status_code)
print('Response URL', response_index.url)
リクエストはリダイレクトを自動的に処理できるので、ログインプロセスをシミュレートするためにallow_redirectsパラメータを追加し、Falseに設定してリダイレクトが自動的に処理されないようにする必要があります。ここでは、ログイン後に返される応答がresponse_loginに割り当てられているため、response_login cookieを呼び出します。ウェブサイトのCookie情報を取得できます。ここでは、リクエストにより、レスポンスヘッダーのset-cookieフィールドの解析とCookieの設定が自動的に行われるため、レスポンスヘッダーのコンテンツを手動で解析する必要はありません。response_loginオブジェクトのcookiesプロパティを使用して直接取得できます。クッキー。
では、リクエストのgetメソッドを再度使用して、ウェブサイトのINDEX_URLをリクエストしますが、これは以前とは異なります。getメソッドはパラメータcookieを追加します。これは、最初のシミュレーションログイン後に取得されたcookieなので、2番目のリクエストは最初のシミュレートされたログインによって取得されたCookie情報を保持することは可能です。このとき、WebサイトはCookieのセッションID情報に従って同じセッションを見つけ、すでにログインしていることを確認して、正しい結果を返します。
ここでも最終的なURLが出力されます。INDEX_URLの場合は、シミュレートされたログインが成功し、有効なデータが取得されたことを意味します。それ以外の場合は、シミュレートされたログインが失敗したことを意味します。
操作の結果を確認します。
Cookies <RequestsCookieJar[<Cookie sessionid=psnu8ij69f0ltecd5wasccyzc6ud41tc for login2.scrape.cuiqingcai.com/>]>
Response Status 200
Response URL https://login2.scrape.cuiqingcai.com/page/1
今回は問題ありませんが、今回はそのURLがINDEX_URLであり、シミュレートされたログインが成功しました。同時に、response_indexのテキスト属性をさらに出力して、取得が成功したかどうかを確認できます。
以降のクロールも同じ方法でクロールできます。
しかし、この実装方法は実際にはかなり面倒であり、Cookieを毎回処理して渡す必要があることがわかりました。
はい、リクエストの組み込みSessionオブジェクトを直接使用して、Cookieを自動的に処理することができます。Sessionオブジェクトを使用した後、リクエストは、各リクエストの後に設定する必要があるCookieを自動的に保存し、次のリクエストで自動的に実行します。これは、より便利なSessionオブジェクトの維持を支援することと同じです。
したがって、コードは次のように簡略化できます。
import requests
from urllib.parse import urljoin
BASE_URL = 'https://login2.scrape.cuiqingcai.com/'
LOGIN_URL = urljoin(BASE_URL, '/login')
INDEX_URL = urljoin(BASE_URL, '/page/1')
USERNAME = 'admin'
PASSWORD = 'admin'
session = requests.Session()
response_login = session.post(LOGIN_URL, data={
'username': USERNAME,
'password': PASSWORD
})
cookies = session.cookies
print('Cookies', cookies)
response_index = session.get(INDEX_URL)
print('Response Status', response_index.status_code)
print('Response URL', response_index.url)
ご覧のとおり、Cookieの処理と配信を気にする必要はありません。Sessionオブジェクトを宣言し、リクエストが呼び出されるたびに、Sessionオブジェクトのpostまたはgetメソッドを直接使用します。
実行効果はまったく同じで、結果は次のとおりです。
Cookies <RequestsCookieJar[<Cookie sessionid=ssngkl4i7en9vm73bb36hxif05k10k13 for login2.scrape.cuiqingcai.com/>]>
Response Status 200
Response URL https://login2.scrape.cuiqingcai.com/page/1
したがって、書き込みを簡単にするために、Sessionオブジェクトを直接使用してリクエストを行うことをお勧めします。これにより、Cookieの操作を気にする必要がなくなり、実装がより便利になります。
このケースは全体的に比較的単純ですが、暗号化されたパラメーターなど、確認コードなどのより複雑なWebサイトに遭遇した場合、リクエストを直接使用してシミュレートされたログインを処理することは簡単ではありません。ログインできない場合、ページ全体はそうではありません。もう登れませんか?この問題を解決する他の方法はありますか?たとえば、Seleniumを使用してブラウザーをシミュレートし、シミュレートされたログインを実現し、シミュレートされたログインが成功した後にCookieを取得し、取得したCookieをクロールのリクエストに送信できます。
ここではまだ例としてページを取り上げていますが、Seleniumへのシミュレートされたログインを引き渡して実装し、続い
て実装リクエストへのクロールを行うと、コード実装は次のようになります。
from urllib.parse import urljoin
from selenium import webdriver
import requests
import time
BASE_URL = 'https://login2.scrape.cuiqingcai.com/'
LOGIN_URL = urljoin(BASE_URL, '/login')
INDEX_URL = urljoin(BASE_URL, '/page/1')
USERNAME = 'admin'
PASSWORD = 'admin'
browser = webdriver.Chrome()
browser.get(BASE_URL)
browser.find_element_by_css_selector('input[name="username"]').send_keys(USERNAME)
browser.find_element_by_css_selector('input[name="password"]').send_keys(PASSWORD)
browser.find_element_by_css_selector('input[type="submit"]').click()
time.sleep(10)
# get cookies from selenium
cookies = browser.get_cookies()
print('Cookies', cookies)
browser.close()
# set cookies to requests
session = requests.Session()
for cookie in cookies:
session.cookies.set(cookie['name'], cookie['value'])
response_index = session.get(INDEX_URL)
print('Response Status', response_index.status_code)
print('Response URL', response_index.url)
ここでは、Seleniumを使用して最初にChromeブラウザを開き、次にログインページにジャンプしてから、ユーザー名とパスワードの入力をシミュレートしてから、ログインボタンをクリックします。この時点で、ブラウザがログイン成功のプロンプトを表示し、次に正常にジャンプしました。ホームページ。
現時点では、get_cookiesメソッドを呼び出すことにより、現在のブラウザのすべてのCookieを取得できます。これは、ログインが成功した後のCookieです。これらのCookieを使用して、他のデータにアクセスできます。
次に、リクエストのSessionオブジェクトを宣言し、今すぐCookieを走査してSessionオブジェクトのCookieに設定し、Sessionオブジェクトを使用してINDEX_URLをリクエストすると、ジャンプせずに対応する情報を取得できますログインページに移動します。
結果は次のとおりです。
Cookies [{
'domain': 'login2.scrape.cuiqingcai.com', 'expiry': 1589043753.553155, 'httpOnly': True, 'name': 'sessionid', 'path': '/', 'sameSite': 'Lax', 'secure': False, 'value': 'rdag7ttjqhvazavpxjz31y0tmze81zur'}]
Response Status 200
Response URL https://login2.scrape.cuiqingcai.com/page/1
シミュレートされたログインとその後のクロールも成功したことがわかります。したがって、ログインプロセスをシミュレートすることが難しい場合は、SeleniumまたはPyppeteerを使用してブラウザ操作をシミュレートすることもできます。目的は、ログイン後にCookieを取得することです。Cookieの後、これらのCookieを使用してクロールします他のページをフェッチするだけです。
したがって、ここでは、セッション+ Cookie認証に基づくWebサイトの場合、シミュレートされたログインのコアポイントはCookieを取得することです。Cookieは保存するか、継続して使用するために他のプログラムに渡すことができます。Cookieを永続的に保存したり、他の端末に送信して使用したりすることもできます。さらに、Cookieの使用率を向上させるか、禁止の可能性を減らすために、Cookieプールを構築して、Cookieへのランダムアクセスを実現できます。
ケース2
ケース2の場合、JWTベースのWebサイトは通常、フロントエンドとバックエンドの分離を採用します。フロントエンドとバックエンドのデータ転送はAjaxに依存し、ログイン検証はJWTトークンの値に依存します。JWTトークンが有効な場合、サーバー必要なデータを返すことができます。
図に示すように、まずブラウザにログインして、ネットワーク要求プロセスを観察してみましょう。
ここで、ログイン時にリクエストされたURLはhttps://login3.scrape.cuiqingcai.com/api/loginであり、Ajaxを通じてリクエストされ、そのリクエストボディはフォームデータではなくJSON形式のデータであり、ステータスコードが返されます。 200です。
次に、図に示すように、返された結果を確認します。
返された結果は、トークンフィールドを含むJSON形式のデータであることがわかります。結果は次のとおりです。
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6ImFkbWluIiwiZXhwIjoxNTg3ODc3OTQ2LCJlbWFpbCI6ImFkbWluQGFkbWluLmNvbSIsIm9yaWdfaWF0IjoxNTg3ODM0NzQ2fQ.ujEXXAZcCDyIfRLs44i_jdfA3LIp5Jc74n-Wq2udCR8
これは、前のレッスンで説明したJWTの内容です。形式は、「。」で区切られた3部構成です。
では、このJWTの後、後続のデータを取得するにはどうすればよいでしょうか。図に示すように、後続のリクエストの内容を観察してみましょう。
ここでは、データ取得の後続のAjaxリクエストのリクエストヘッダーにAuthorizationフィールドがあることがわかります。結果はjwtになり、JWTのコンテンツが追加されます。返される結果は、JSON形式のデータです。
問題ありません。ログインを
シミュレートするというアイデア全体は単純です。リクエストのログイン結果をシミュレートし、必要なログイン情報を用意して、JWTの結果を取得します。
後続のリクエストでは、Authorizationフィールドがリクエストヘッダーに追加され、値はJWTに対応するコンテンツです。
それでは、コードを使用して次のことを実現します。
import requests
from urllib.parse import urljoin
BASE_URL = 'https://login3.scrape.cuiqingcai.com/'
LOGIN_URL = urljoin(BASE_URL, '/api/login')
INDEX_URL = urljoin(BASE_URL, '/api/book')
USERNAME = 'admin'
PASSWORD = 'admin'
response_login = requests.post(LOGIN_URL, json={
'username': USERNAME,
'password': PASSWORD
})
data = response_login.json()
print('Response JSON', data)
jwt = data.get('token')
print('JWT', jwt)
headers = {
'Authorization': f'jwt {jwt}'
}
response_index = requests.get(INDEX_URL, params={
'limit': 18,
'offset': 0
}, headers=headers)
print('Response Status', response_index.status_code)
print('Response URL', response_index.url)
print('Response Data', response_index.json())
ここでは、ログインインターフェースと、それぞれLOGIN_URLとINDEX_URLのデータを取得するためのインターフェースも定義しています。次に、postリクエストを介してログインをシミュレートします。ここで送信されるデータはJSON形式であるため、jsonパラメータを使用してここに渡します。次に、返された結果に含まれるJWT結果を取得します。2番目のステップでは、リクエストヘッダーを作成し、Authorizationフィールドを設定してJWTに渡すことで、データを正常に取得できます。
結果は次のとおりです。
Response JSON {
'token': 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6ImFkbWluIiwiZXhwIjoxNTg3ODc4NzkxLCJlbWFpbCI6ImFkbWluQGFkbWluLmNvbSIsIm9yaWdfaWF0IjoxNTg3ODM1NTkxfQ.iUnu3Yhdi_a-Bupb2BLgCTUd5yHL6jgPhkBPorCPvm4'}
JWT eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6ImFkbWluIiwiZXhwIjoxNTg3ODc4NzkxLCJlbWFpbCI6ImFkbWluQGFkbWluLmNvbSIsIm9yaWdfaWF0IjoxNTg3ODM1NTkxfQ.iUnu3Yhdi_a-Bupb2BLgCTUd5yHL6jgPhkBPorCPvm4
Response Status 200
Response URL https://login3.scrape.cuiqingcai.com/api/book/?limit=18&offset=0
Response Data {
'count': 9200, 'results': [{
'id': '27135877', 'name': '校园市场:布局未来消费群,决战年轻人市场', 'authors': ['单兴华', '李烨'], 'cover': 'https://img9.doubanio.com/view/subject/l/public/s29539805.jpg', 'score': '5.5'},
...
{
'id': '30289316', 'name': '就算這樣,還是喜歡你,笠原先生', 'authors': ['おまる'], 'cover': 'https://img3.doubanio.com/view/subject/l/public/s29875002.jpg', 'score': '7.5'}]}
ご覧のとおり、ここでJWTのコンテンツが正常に出力され、対応するデータが最終的に取得され、シミュレートされたログインが成功しました。
同様の考え方で、JWT認定Webサイトに遭遇した場合、同様の方法でシミュレートされたログインを実装することもできます。もちろん、一部のページはより複雑で、特定の状況の特定の分析が必要な場合があります。
総括する
上記では、2つの例を通じてログインクロールのシミュレーションのプロセスを示しましたが、同様のアイデアを使用して、今後この状況を解決できます。
コード:https://github.com/Python3WebSpider/ScrapeLogin2、https://github.com/Python3WebSpider/ScrapeLogin3。