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