Datawhale第十二期組隊學習--Python爬蟲編程實踐 Task03:session和cookie、代理、selenium自動化 拔高:丁香園留言板爬取

一. session和cookie

常用的 web 容器有 Nginx 、 Apache 、 Tomcat 、Weblogic 、 Jboss 、 Resin 等等

http 1.0
  • HTTP1.0的特點是無狀態無鏈接的
  • 無狀態就是指 HTTP 協議對於請求的發送處理是沒有記憶功能的,也就是說每次 HTTP 請求到達服務端,服務端都不知道當前的客戶端(瀏覽器)到底是一個什麼狀態。客戶端向服務端發送請求後,服務端處理這個請求,然後將內容響應回客戶端,完成一次交互,這個過程是完全相互獨立的,服務端不會記錄前後的狀態變化,也就是缺少狀態記錄。
session和cookies

Session 是會話的意思,會話是產生在服務端的,用來保存當前用戶的會話信息,而 Cookies 是保存在客戶端(瀏覽器),有了 Cookie 以後,客戶端(瀏覽器)再次訪問服務端的時候,會將這個 Cookie 帶上,這時,服務端可以通過 Cookie 來識別本次請求到底是誰在訪問。

可以簡單理解爲 Cookies 中保存了登錄憑證,我們只要持有這個憑證,就可以在服務端保持一個登錄狀態。

在爬蟲中,有時候遇到需要登錄才能訪問的網頁,只需要在登錄後獲取了 Cookies ,在下次訪問的時候將登錄後獲取到的 Cookies 放在請求頭中,這時,服務端就會認爲我們的爬蟲是一個正常登錄用戶。

session
  • 那麼,Cookies 是如何保持會話狀態的呢?

    在客戶端(瀏覽器)第一次請求服務端的時候,服務端會返回一個請求頭中帶有 Set-Cookie 字段的響應給客戶端(瀏覽器),用來標記是哪一個用戶,客戶端(瀏覽器)會把這個 Cookies 給保存起來。
    當我們輸入好用戶名和密碼時,客戶端會將這個 Cookies 放在請求頭一起發送給服務端,這時,服務端就知道是誰在進行登錄操作,並且可以判斷這個人輸入的用戶名和密碼對不對,如果輸入正確,則在服務端的 Session 記錄一下這個人已經登錄成功了,下次再請求的時候這個人就是登錄狀態了。

    如果客戶端傳給服務端的 Cookies 是無效的,或者這個 Cookies 根本不是由這個服務端下發的,或者這個 Cookies 已經過期了,那麼接下里的請求將不再能訪問需要登錄後才能訪問的頁面。

所以, Session 和 Cookies 之間是需要相互配合的,一個在服務端,一個在客戶端。

cookie

Cookies到底有哪些內容,具體操作方式還是在 Chrome 中按 F12 打開開發者工具,選擇 Application 標籤,點開 Cookies 這一欄。

  • Name:這個是 Cookie 的名字。一旦創建,該名稱便不可更改。
  • Value:這個是 Cookie 的值。
  • Domain:這個是可以訪問該 Cookie 的域名。例如,如果設置爲 .jd.com ,則所有以 jd.com ,結尾的域名都可以訪問該Cookie。
  • Max Age:Cookie 失效的時間,單位爲秒,也常和 Expires 一起使用。 Max Age 如果爲正數,則在 Max Age 秒之後失效,如果爲負數,則關閉瀏覽器時 Cookie 即失效,瀏覽器也不會保存該 Cookie 。
  • Path:Cookie 的使用路徑。如果設置爲 /path/ ,則只有路徑爲 /path/ 的頁面可以訪問該 Cookie 。如果設置爲 / ,則本域名下的所有頁面都可以訪問該 Cookie 。
  • Size:Cookie 的大小。
  • HTTPOnly:如果此項打勾,那麼通過 JS 腳本將無法讀取到 - Cookie 信息,這樣能有效的防止 XSS 攻擊,竊取 Cookie 內容,可以增加 Cookie 的安全性。
  • Secure:如果此項打勾,那麼這個 Cookie 只能用 HTTPS 協議發送給服務器,用 HTTP 協議是不發送的。

那麼有的網站爲什麼這次關閉了,下次打開的時候還是登錄狀態呢?

這就要說到 Cookie 的持久化了,其實也不能說是持久化,就是 Cookie 失效的時間設置的長一點,比如直接設置到 2099 年失效,這樣,在瀏覽器關閉後,這個 Cookie 是會保存在我們的硬盤中的,下次打開瀏覽器,會再從我們的硬盤中將這個 Cookie 讀取出來,用來維持用戶的會話狀態。

第二個問題產生了,服務端的會話也會無限的維持下去麼,當然不會,這就要在 Cookie 和 Session 上做文章了, Cookie 中可以使用加密的方式將用戶名記錄下來,在下次將 Cookies 讀取出來由請求發送到服務端後,服務端悄悄的自己創建一個用戶已經登錄的會話,這樣我們在客戶端看起來就好像這個登錄會話是一直保持的。

一個重要概念

當我們關閉瀏覽器的時候會自動銷燬服務端的會話,這個是錯誤的,因爲在關閉瀏覽器的時候,瀏覽器並不會額外的通知服務端說,我要關閉了,你把和我的會話銷燬掉吧。

因爲服務端的會話是保存在內存中的,雖然一個會話不會很大,但是架不住會話多啊,硬件畢竟是會有限制的,不能無限擴充下去的,所以在服務端設置會話的過期時間就非常有必要。

當然,有沒有方式能讓瀏覽器在關閉的時候同步的關閉服務端的會話,當然是可以的,我們可以通過腳本語言 JS 來監聽瀏覽器關閉的動作,當瀏覽器觸發關閉動作的時候,由 JS 像服務端發起一個請求來通知服務端銷燬會話。

由於不同的瀏覽器對 JS 事件的實現機制不一致,不一定保證 JS 能監聽到瀏覽器關閉的動作,所以現在常用的方式還是在服務端自己設置會話的過期時間

代碼:

# 實戰案例:模擬登錄163
import time
from selenium import webdriver
from selenium.webdriver.common.by import By

"""
使用selenium進行模擬登陸
1.初始化ChromeDriver
2.打開163登陸頁面
3.找到用戶名的輸入框,輸入用戶名
4.找到密碼框,輸入密碼
5.提交用戶信息
"""
# driver打卡163登錄地址
# 測試寫真實的name, passwd 可以登錄
name = '*'
passwd = '*'
# webdriver 版本不對,打開chrome就有問題
# chorme版本81.0.4044.122(正式版本) (32 位), driver版本81.0.4044.69(win)
driver = webdriver.Chrome('./chromedriver.exe')
driver.get('https://mail.163.com/')
# 將窗口調整最大
driver.maximize_window()
# 休息5s
time.sleep(5)
current_window_1 = driver.current_window_handle
print(current_window_1)

# 打卡輸入用戶名,密碼登錄
button = driver.find_element(by=By.ID, value='lbNormal')
button.click()
driver.switch_to.frame(driver.find_element(by=By.XPATH, value="//iframe[starts-with(@id, 'x-URS-iframe')]"))

# 輸入用戶名,密碼,模擬登錄
# email = driver.find_element_by_name('email')
email = driver.find_element(by=By.NAME, value='email')
#email = driver.find_element_by_xpath('//input[@name="email"]')
email.send_keys(name)
# password = driver.find_element_by_name('password')
password = driver.find_element(by=By.NAME, value='password')
#password = driver.find_element_by_xpath("//input[@name='password']")
password.send_keys(passwd)
# submit = driver.find_element_by_id("dologin")
submit = driver.find_element(by=By.ID, value="dologin")
time.sleep(15)
submit.click()
time.sleep(10)
# print(driver.page_source)
driver.quit()

二. IP 代理

爲什麼會出現IP被封
網站爲了防止被爬取,會有反爬機制,對於同一個IP地址的大量同類型的訪問,會封鎖IP,過一段時間後,才能繼續訪問

如何應對IP被封的問題
有幾種套路:

  • 修改請求頭,模擬瀏覽器(而不是代碼去直接訪問)去訪問
  • 採用代理IP並輪換
  • 設置訪問時間間隔

代碼:

import requests
import re
import json
from lxml import etree
from bs4 import BeautifulSoup


# 獲取ip list
def get_ip_list():
    url = "https://www.xicidaili.com/"
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.122 Safari/537.36',
    }

    res = requests.get(url, headers=headers)
    # print(res.text)
    html = etree.HTML(res.text)
    ip_list = html.xpath('//*[@id="ip_list"]/tr')
    print('ip list=', len(ip_list))
    if ip_list:
        print('ip=', ip_list[0].xpath('//td[2]/text()'))


def open_proxy_url(url):
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.122 Safari/537.36',
    }
    try:
        r = requests.get(url, headers=headers, timeout=20)
        r.raise_for_status()
        r.encoding = r.apparent_encoding
        return r.text
    except:
        print('無法訪問網頁' + url)


def get_proxy_ip(response):
    proxy_ip_list = []
    soup = BeautifulSoup(response, 'html.parser')
    proxy_ips = soup.find(id='ip_list').find_all('tr')
    for proxy_ip in proxy_ips:
        if len(proxy_ip.select('td')) >= 8:
            # print('==', proxy_ip.select('td'))
            ip = proxy_ip.select('td')[1].text
            port = proxy_ip.select('td')[2].text
            protocol = proxy_ip.select('td')[5].text
            if protocol in ('HTTP', 'HTTPS', 'http', 'https'):
                proxy_ip_list.append(f'{protocol}://{ip}:{port}')
    return proxy_ip_list


# 使用代理ip打開百度頁面
def open_url_using_proxy(url, proxy):
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.122 Safari/537.36',
    }
    proxies = {}
    if proxy.startswith('HTTPS'):
        proxies['https'] = proxy
    else:
        proxies['http'] = proxy
    try:
        r = requests.get(url, headers=headers, proxies=proxies, timeout=10)
        r.raise_for_status()
        r.encoding = r.apparent_encoding
        return r.text, r.status_code
    except:
        print('無法訪問網頁' + url)
        return False


# 檢查代理ip的有效性
def check_proxy_avaliability(proxy):
    url = 'http://www.baidu.com'
    result = open_url_using_proxy(url, proxy)
    VALID_PROXY = False
    if result:
        text, status_code = result
        if status_code == 200:
            r_title = re.findall('<title>.*</title>', text)
            if r_title:
                if r_title[0] == '<title>百度一下,你就知道</title>':
                    VALID_PROXY = True
        if VALID_PROXY:
            check_ip_url = 'https://jsonip.com/'
            try:
                text, status_code = open_url_using_proxy(check_ip_url, proxy)
            except:
                return

            print('有效代理IP: ' + proxy)
            with open('valid_proxy_ip.txt', 'a') as f:
                f.writelines(proxy)
            try:
                source_ip = json.loads(text).get('ip')
                print(f'源IP地址爲:{source_ip}')
                print('='*40)
            except:
                print('返回的非json,無法解析')
                print(text)
    else:
        print('無效代理IP: ' + proxy)


if __name__ == '__main__':
    # 獲取代理ip
    # proxy_url = 'https://www.xicidaili.com/'
    # text = open_proxy_url(proxy_url)
    # proxy_ip_filename = 'proxy_ip.txt'
    # with open(proxy_ip_filename, 'w') as f:
    #     f.write(text)
    # text = open(proxy_ip_filename, 'r').read()
    # proxy_ip_list = get_proxy_ip(text)
    # print(len(proxy_ip_list))
    # print(proxy_ip_list)
    #
    # # 測試代理ip打開
    # url = 'http://www.baidu.com'
    # text = open_url_using_proxy(url, proxy_ip_list[0])

    # Data whale test
    proxy_url = 'https://www.xicidaili.com/'
    proxy_ip_filename = 'proxy_ip.txt'
    text = open(proxy_ip_filename, 'r').read()
    proxy_ip_list = get_proxy_ip(text)
    for proxy in proxy_ip_list:
        check_proxy_avaliability(proxy)

三. selenium自動化

selenium是什麼:一個自動化測試工具(大家都是這麼說的)
selenium應用場景:用代碼的方式去模擬瀏覽器操作過程(如:打開瀏覽器、在輸入框裏輸入文字、回車等),在爬蟲方面很有必要
準備工作:
安裝selenium(pip install selenium)
安裝chromedriver(一個驅動程序,用以啓動chrome瀏覽器,具體的驅動程序需要對應的驅動,在官網上可以找到下載地址)

# 基本的操作
from selenium import webdriver  # 啓動瀏覽器需要用到

driver = webdriver.Chrome(".chromedriver.exe")
driver.get("http://www.python.org")
driver.close()  # 關閉瀏覽器一個Tab

查找元素

element = driver.find_element_by_name("q")

頁面交互(鍵盤輸入)

element.send_keys(“some text”)  # 往一個可以輸入對象中輸入“some text”

element.send_keys(Keys.RETURN)  # 模擬鍵盤迴車

# 一般來說,這種方式輸入後會一直存在,而要清空某個文本框中的文字,就需要:
element.clear()  # 清空element對象中的文字

等待頁面加載(wait)

  • 應用場景:含有ajax加載的page!因爲在這種情況下,頁面內的某個節點並不是在一開始就出現了,而在這種情況下,就不能“查找元素”,元素選擇不到,就不好進行交互操作!等待頁面加載這兩個模塊經常是一起導入的:
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
  • 顯示等待:觸發某個條件後才能夠執行後續的代碼
driver = webdriver.Firefox()
driver.get("http://somedomain/url_that_delays_loading")
try:    
    element = WebDriverWait(driver, 10).until(           
        EC.presence_of_element_located((By.ID, "myDynamicElement")))
finally:    
    driver.quit()
#其中,presence_of_element_located是條件,By.ID是通過什麼方式來確認元素(這個是通過id),"myDynamicElement"這個就是某個元素的ID
  • 隱示等待:設置某個具體的等待時間
driver = webdriver.Firefox()
driver.implicitly_wait(10) # seconds
driver.get("http://somedomain/url_that_delays_loading")
myDynamicElement = driver.find_element_by_id("myDynami")

四. 小作業

Q1: 怎麼在ip被封之後實現自動更換代理池內的代理?
A1: 用random.choice 隨機選取ip

Q2: 如何用一句通俗的語言解釋清楚request、beautifulsoup和selenium三者與瀏覽器之間的關係?
A2: BeautifulSoup:處理速度快,同時可以連續查找,主要用於靜態網頁

經過BeautifulSoup處理以後,編碼方式都變成了Unicode,需要將其變成所需的編碼方式:可以利用encode(‘需要的編碼’),還可以利用 BeautifulSoup(網頁/html, lxml/xml”).prettify(‘需要的編碼’) 可以利用soup.originalencoding檢測原來的編碼。 Selenium:主要用於動態網頁,查找速度慢,解析時要注意 .findelements_byxpath和.findelement_by_xpath有區別,同時利用瀏覽器時要配置。 .PhantomJS: drive=webdriver.PhantomJS(‘D:\Anaconda2\phantomjswindows\binphantomjs.exe’)

Q3: 構建好代理池後,如何在一次爬蟲中自動切換代理? 比如代理無效,或者代理ip被封,這時自動切換下一個ip。
A3: 首先你要有一個ip代理池(如果比較豪可以自己買真實ip自行搭建,好處獨享有技術門檻,或者找第三方ip代理商對接,好吃廉價,但ip不獨享), 真實ip需要自己寫程序來代理轉發,第三方ip代理商則會提供相關轉發API,直接調用就可以,這東西沒什麼技術難度

Q4: ip_list.append(f’{protpcol}😕/{ip}:{port}’)這裏的f是格式化?
A4:是的

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章