Python網絡爬蟲-Datawhale組隊task3

ip代理

IP被封

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

應對:

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

如何獲取代理IP地址

  • 從網站獲取:https://www.xicidaili.com/
  • inspect -> 鼠標定位:要獲取的代理IP地址,屬於class = "odd"標籤的內容(實驗發現,有些並非class = “odd”,而是…,這些數據沒有被獲取 class = "odd"奇數的結果,而沒有class = "odd"的是偶數的結果)
  • 通過bs4的find_all(‘tr’)來獲取所有IP
import re
import json
from bs4 import BeautifulSoup

import requests
import time


class GetIp:
    def __init__(self):
        self.user_agent = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36'
        self.headers = {'User-Agent': self.user_agent}

    def open_proxy_url(self, url):
        try:
            req = requests.get(url, headers=self.headers, timeout=20)
            req.raise_for_status()
            req.coding = req.apparent_encoding
            return req.text
        except:
            print('error:' + url)

    def get_proxy_ip(self, response):
        proxy_ip_list = []
        soup = BeautifulSoup(response, 'html.parser')
        # proxy_ips = soup.select('.odd')
        proxy_ips = soup.find(id='ip_list').find_all('tr')
        # print(proxy_ips)

        for proxy_ip in proxy_ips:
            if len(proxy_ip.select('td')) >= 8:
                ip = proxy_ip.select('td')[1].text
                port = proxy_ip.select('td')[2].text
                protocol = proxy_ip.select('td')[5].text
                # ip = proxy_ip.select('td')[1].text
                # port = proxy_ip.select('td')[2].text
                # protocol = proxy_ip.select('td')[5].text
                ip = proxy_ip
                if protocol in ('HTTP', 'HTTPS'):
                    proxy_ip_list.append(f'{protocol}://{ip}:{port}')
        return proxy_ip_list

    def open_url_using_proxy(self, url, proxy):
        proxies = {}
        if proxy.startswith(('HTTP', 'https')):
            proxies['https'] = proxy
        else:
            proxies['http'] = proxy
        try:
            res = requests.get(url, headers=self.headers)
            res.raise_for_status()
            res.encoding = res.apparent_encoding
            return (res.text, res.status_code)
        except:
            print('error url' + url)
            print('error ip' + proxy)
            return False

    def check_proxy_avaliability(self, proxy):
        url = 'http://www.baidu.com'
        result = self.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 = self.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__':
    proxy_url = 'https://www.xicidaili.com/'
    getip = GetIp()
    text = getip.open_proxy_url(proxy_url)
    proxy_ip_filename = 'proxy_ip.txt'
    with open(proxy_ip_filename, 'w') as f:
        f.write(str(text))
    text = open(proxy_ip_filename, 'r').read()
    proxy_ip_list = getip.get_proxy_ip(text)
    print(proxy_ip_list)
    for proxy in proxy_ip_list:
        getip.check_proxy_avaliability(proxy)
使用代理

proxies的格式是一個字典:{‘http’: ‘http://IP:port‘,‘https’:'https://IP:port‘},
把它直接傳入requests的get方法中即可:
web_data = requests.get(url, headers=headers, proxies=proxies)

確認代理IP地址有效性

無論是免費還是收費的代理網站,提供的代理IP都未必有效,我們應該驗證一下,有效後,再放入我們的代理IP池中,以下通過幾種方式:訪問網站,得到的返回碼是200真正的訪問某些網站,獲取title等,驗證title與預計的相同訪問某些可以提供被訪問IP的網站,類似於“查詢我的IP”的網站,查看返回的IP地址是什麼驗證返回碼

http和https代理

可以看到proxies中有兩個鍵值對:
{‘http’: ‘http://IP:port‘,‘https’:'https://IP:port‘}
其中 HTTP 代理,只代理 HTTP 網站,對於 HTTPS 的網站不起作用,也就是說,用的是本機 IP,反之亦然。

使用的驗證的網站是https://jsonip.com, 是HTTPS網站所以探測到的有效代理中,如果是https代理,則返回的是代理地址;如果是http代理,將使用本機IP進行訪問,返回的是我的公網IP地址

selenium

selenium:一個自動化測試工具
selenium應用場景:用代碼的方式去模擬瀏覽器操作過程(如:打開瀏覽器、在輸入框裏輸入文字、回車等),在爬蟲方面很有必要

準備工作:

  • 安裝selenium(pip install selenium)
  • 安裝chromedriver(一個驅動程序,用以啓動chrome瀏覽器,具體的驅動程序需要對應的驅動,在官網上可以找到下載地址)
from selenium import webdriver 
from selenium.webdriver.common.keys import Keys
driver = webdriver.Chrome("...")
driver.get("http://www.python.org")
driver.close()
driver.quit()

查找

定位到具體的某個元素基本一樣,只不過,調用者是driver

交互

找到元素後,就是進行“交互”

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

driver = webdriver.Firefox()
driver.implicitly_wait(10) # seconds
driver.get("http://somedomain/url_that_delays_loading")
myDynamicElement = driver.find_element_by_id("myDynami")

session和cookie

靜態網頁:

html網頁,直接部署到或者是放到某個 web 容器上,就可以在瀏覽器通過鏈接直接訪問到了,常用的 web 容器有 Nginx 、 Apache 、 Tomcat 、Weblogic 、 Jboss 、 Resin 等等。好處是加載速度快,編寫簡單,訪問的時候對 web 容器基本上不會產生什麼壓力。但是缺點也很明顯,可維護性比較差,不能根據參數動態的顯示內容等等。有需求就會有發展麼,這時動態網頁就應運而生了。

動態網頁:

大家常用的某寶、某東、拼夕夕等網站都是由動態網頁組成的。
動態網頁可以解析 URL 中的參數,或者是關聯數據庫中的數據,顯示不同的網頁內容。現在各位同學訪問的網站大多數都是動態網站,它們不再簡簡單單是由 HTML 堆砌而成,可能是由 JSP 、 PHP 等語言編寫的,當然,現在很多由前端框架編寫而成的網頁小編這裏也歸屬爲動態網頁。
說到動態網頁,各位同學可能使用頻率最高的一個功能是登錄,像各種電商類網站,肯定是登錄了以後才能下單買東西。

http1.0

HTTP1.0的特點是無狀態無鏈接的
無狀態就是指 HTTP 協議對於請求的發送處理是沒有記憶功能的,也就是說每次 HTTP 請求到達服務端,服務端都不知道當前的客戶端(瀏覽器)到底是一個什麼狀態。客戶端向服務端發送請求後,服務端處理這個請求,然後將內容響應回客戶端,完成一次交互,這個過程是完全相互獨立的,服務端不會記錄前後的狀態變化,也就是缺少狀態記錄。這就產生了上面的問題,服務端如何知道當前在瀏覽器面前操作的這個人是誰?其實,在用戶做登錄操作的時候,服務端會下發一個類似於 token 憑證的東西返回至客戶端(瀏覽器),有了這個憑證,才能保持登錄狀態。

Session和cookies

Session

Session 是會話的意思,會話是產生在服務端的,用來保存當前用戶的會話信息,而 Cookies 是保存在客戶端(瀏覽器),有了 Cookie 以後,客戶端(瀏覽器)再次訪問服務端的時候,會將這個 Cookie 帶上,這時,服務端可以通過 Cookie 來識別本次請求到底是誰在訪問。
可以簡單理解爲 Cookies 中保存了登錄憑證,我們只要持有這個憑證,就可以在服務端保持一個登錄狀態。
在爬蟲中,有時候遇到需要登錄才能訪問的網頁,只需要在登錄後獲取了 Cookies ,在下次訪問的時候將登錄後獲取到的 Cookies 放在請求頭中,這時,服務端就會認爲我們的爬蟲是一個正常登錄用戶。

在客戶端(瀏覽器)第一次請求服務端的時候,服務端會返回一個請求頭中帶有 Set-Cookie 字段的響應給客戶端(瀏覽器),用來標記是哪一個用戶,客戶端(瀏覽器)會把這個 Cookies 給保存起來。

當我們輸入好用戶名和密碼時,客戶端會將這個 Cookies 放在請求頭一起發送給服務端,這時,服務端就知道是誰在進行登錄操作,並且可以判斷這個人輸入的用戶名和密碼對不對,如果輸入正確,則在服務端的 Session 記錄一下這個人已經登錄成功了,下次再請求的時候這個人就是登錄狀態了。

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

cookies

Session 和 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 能監聽到瀏覽器關閉的動作,所以現在常用的方式還是在服務端自己設置會話的過期時間

import time

from selenium import webdriver
from selenium.webdriver.common.by import By
name = '*'
passwd = '*'
driver = webdriver.Chrome('./chromedriver')
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_id('lbNormal')
button.click()

driver.switch_to.frame(driver.find_element_by_xpath("//iframe[starts-with(@id, 'x-URS-iframe')]"))

email = driver.find_element_by_name('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_xpath("//input[@name='password']")
password.send_keys(passwd)
submit = driver.find_element_by_id("dologin")
time.sleep(15)
submit.click()
time.sleep(10)
print(driver.page_source)
driver.quit()

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