gevent模組
示例程式碼:
特點: 可以識別所有阻塞
from gevent import monkey
monkey.patch_all()
import gevent
import requests
from lxml import etree
import time
# 傳送請求
def get_request(url):
page_text = requests.get(url).text
tree = etree.HTML(page_text)
print(len(tree.xpath('//div[1]//text()')))
return page_text
# 解析資料
def parse(page_text):
tree = etree.HTML(page_text)
print(tree.xpath('//div[1]//text()'))
start = time.time()
g1 = gevent.spawn(get_request, 'http://127.0.0.1:5000/index')
g2 = gevent.spawn(get_request, 'http://127.0.0.1:5000/hxbs')
gevent.joinall([g1, g2])
print(time.time() - start)
asyncio模組
安裝: pip install asyncio
特點: 只能識別支援非同步的模組的阻塞
協程物件
import asyncio
from time import sleep
# 特殊的函式: 如果一個函式的定義被async關鍵字修飾,則該函式是一個特殊函式
async def get_request(url):
print('正在請求:', url)
sleep(1)
print('請求結束:', url)
# 特殊函式被呼叫後,函式內部的語句不會立即執行
# 特殊函式的呼叫返回一個協程物件
c = get_request('www.1.com')
# 結論1: 協程物件 == 特殊的函式
任務物件
任務物件其實就是對協程物件的進一步封裝,並且可以給任務物件繫結回撥
結論2: 任務物件 == 高階的協程物件 == 特殊的函式
# 定義回撥函式,接收的引數是任務物件,任務物件執行結束後執行
def parse(task):
ret = task.result() # 取得任務物件(特殊函式)的返回值
# 創一個任務物件: 基於協程物件建立
# task就是一個任務物件
task = asyncio.ensure_future(c)
# 繫結回撥函式
task.add_done_callback(parse)
事件迴圈物件
作用: 將其內部註冊的任務物件進行非同步執行
# 建立一個事件迴圈物件
loop = asyncio.get_event_loop()
# 將任務物件註冊到事件迴圈物件中並且開啟事件迴圈
loop.run_until_complete(task)
注意: 事件迴圈物件中註冊多個任務物件時,需要使用async.wait()對任務列表進行掛起操作
loop.run_until_complete(async.wait(task_list))
編碼流程:
- 定義特殊函式
- 建立協程物件
- 封裝任務物件
- 建立事件迴圈物件
- 將任務物件註冊到事件迴圈物件中並且開啟事件迴圈
注意: 在特殊函式內部的實現語句中,不可以出現不支援非同步的模組對應的程式碼,否則就會終止多工非同步協程的非同步效果
注意: reuqests模組不支援非同步,需要使用aiothttp模組進行爬取,此模組使用方法和requests高度相似,
示例程式碼:
import asyncio
import time
async def get_request(url):
print('正在請求:', url)
# time模組不支援非同步
# time.sleep(1)
# 需要使用await關鍵字對阻塞操作進行等待
await asyncio.sleep(1)
print('請求結束:', url)
urls = [
'www.1.com',
'www.2.com',
'www.3.com'
]
task_list = [] # 存放多個任務物件的列表
for url in urls:
c = get_request(url)
task = asyncio.ensure_future(c)
task_list.append(task)
# 建立一個事件迴圈物件
loop = asyncio.get_event_loop()
# 將任務物件註冊到事件迴圈物件中並且開啟事件迴圈
loop.run_until_complete(asyncio.wait(task_list))
aiohttp模組
安裝: pip install aiohttp
特點: 支援非同步的網路請求模組
編碼流程:
- 寫基本架構:
# aiohttp建立的物件都需要關閉,使用with
with aiohttp.ClientSession() as cs:
# 注意get/post: proxy = 'http://ip:port'
# 其餘引數與requests模組一致
with cs.get(url) as response:
# text()字串形式的響應資料
# read()二進位制的響應資料
page_text = response.text()
return page_text
- 補充細節:
新增async關鍵字: 每一個with前加上async
新增await關鍵字: 在每一步的阻塞操作前加上await
- 請求(get(), post()等)
- 獲取相應資料(text(), read())
asyncio + aiohttp 實現多工協程爬蟲:
簡易伺服器:
# 搭建簡易伺服器
from flask import Flask, render_template
from time import sleep
app = Flask(__name__)
@app.route('/hxbs')
def index_hxbs():
sleep(2)
return render_template('測試.html')
@app.route('/index')
def index_ceshi():
sleep(2)
return render_template('測試.html')
app.run(debug=True)
非同步爬蟲
# 非同步爬蟲
import asyncio
import aiohttp
import time
from lxml import etree
async def get_request(url):
async with aiohttp.ClientSession() as cs:
async with await cs.get(url) as response:
page_text = await response.text()
return page_text
def parse(task):
page_text = task.result()
tree = etree.HTML(page_text)
data = tree.xpath('//div[2]//text()')[0]
print(data)
urls = [
'http://127.0.0.1:5000/hxbs',
'http://127.0.0.1:5000/hxbs',
'http://127.0.0.1:5000/index',
'http://127.0.0.1:5000/index',
]
start = time.time()
task_list = []
for url in urls:
c = get_request(url) # 協程物件
task = asyncio.ensure_future(c) # 任務物件
task.add_done_callback(parse) # 繫結回撥,用於資料解析
task_list.append(task)
loop = asyncio.get_event_loop() # 事件迴圈物件
loop.run_until_complete(asyncio.wait(task_list)) # 註冊任務
print('總耗時:', time.time() - start)
selenium模組
概念: 基於瀏覽器自動化的模組,都用於自動化測試
特性: 不支援非同步,效率較低
selenium和爬蟲之間的關聯:
- 便捷的爬取到動態載入的資料
可見即可得 - 便捷的實現模擬登入
擴充: Appium基於手機的自動化的模組
基本使用:
-
環境安裝:
pip install selenium
-
下載瀏覽器的驅動程式
# google瀏覽器驅動程式
http://chromedriver.storage.googleapis.com/index.html
# 驅動程式與瀏覽器版本對映關係
https://blog.csdn.net/huilan_same/article/details/51896672
演示程式碼:
from selenium import webdriver
from time import sleep
# 後面是你的瀏覽器驅動位置,記得前面加r'','r'是防止字元轉義的
driver = webdriver.Chrome(r'chromedriver.exe')
# 用get開啟百度頁面
driver.get("http://www.baidu.com")
# 查詢頁面的“設定”選項,並進行點選
driver.find_elements_by_link_text('設定')[0].click()
sleep(2)
# # 開啟設定後找到“搜尋設定”選項,設定為每頁顯示50條
driver.find_elements_by_link_text('搜尋設定')[0].click()
sleep(2)
# 選中每頁顯示50條
m = driver.find_element_by_id('nr')
sleep(2)
m.find_element_by_xpath('//*[@id="nr"]/option[3]').click()
m.find_element_by_xpath('.//option[3]').click()
sleep(2)
# 點選儲存設定
driver.find_elements_by_class_name("prefpanelgo")[0].click()
sleep(2)
# 處理彈出的警告頁面 確定accept() 和 取消dismiss()
driver.switch_to.alert.accept()
sleep(2)
# 找到百度的輸入框,並輸入 美女
driver.find_element_by_id('kw').send_keys('美女')
sleep(2)
# 點選搜尋按鈕
driver.find_element_by_id('su').click()
sleep(2)
# 關閉瀏覽器
driver.quit()
基本使用
from selenium import webdriver
import time
# 例項化某一款瀏覽器物件
browser = webdriver.Chrome(executable_path=r'chromedriver.exe')
# 基於瀏覽器發起請求
browser.get('https://www.jd.com')
# 商品搜尋
# 標籤定位
search_input = browser.find_element_by_id('key')
# 向定位到的標籤中錄入資料
search_input.send_keys('python')
# 點選搜尋按鈕
btn = browser.find_element_by_xpath('//*[@id="search"]/div/div[2]/button/i')
btn.click()
# 滾輪滑動(JS注入)
browser.execute_script('window.scrollTo(0, document.body.scrollHeight)')
time.sleep(2)
# 關閉瀏覽器
browser.quit()
爬取動態載入的資料
from selenium import webdriver
from lxml import etree
import time
# 例項化某一款瀏覽器物件
browser = webdriver.Chrome(executable_path=r'chromedriver.exe')
# 基於瀏覽器發起請求
browser.get('https://www.fjggfw.gov.cn/Website/JYXXNew.aspx')
# page_source: 當前頁面所有的頁面原始碼資料
page_text = browser.page_source
# 儲存前三頁對應的頁面原始碼資料
all_page_text = [page_text]
for i in range(2, 4):
next_page_btn = browser.find_element_by_xpath(f'//body//a[@onclick="return kkpager._clickHandler({i})"]')
next_page_btn.click()
time.sleep(1)
page_text = browser.page_source
all_page_text.append(page_text)
for page_text in all_page_text:
tree = etree.HTML(page_text)
title = tree.xpath('//*[@id="list"]/div[1]/div/h4/a/text()')[0]
print(title)
動作鏈
注意:
在使用find系列的函式進行標籤定位時,如果出現NoSuchElementException錯誤怎麼處理?
如果定位的標籤是存在於iframe標籤之下(子頁面)中,則在進行指定標籤定位的時,必須使用switch_to.frame(frame_id)的操作才可.
from selenium import webdriver
from selenium.webdriver import ActionChains
import time
# 例項化某一款瀏覽器物件
browser = webdriver.Chrome(executable_path=r'chromedriver.exe')
# 基於瀏覽器發起請求
browser.get('https://www.runoob.com/try/try.php?filename=jqueryui-api-droppable')
# NoSuchElementException: 定位標籤時出現此錯誤,考慮是否為子頁面內的標籤
browser.switch_to.frame('iframeResult') # 引數是iframe標籤的id屬性值
div_tag = browser.find_element_by_id('draggable')
# 基於動作鏈實現滑動操作
# 例項化一個動作鏈物件: 指定瀏覽器
action = ActionChains(browser)
# 點選且長按
action.click_and_hold(div_tag)
for i in range(4):
# perform()表示讓動作鏈立即執行
action.move_by_offset(25, 0).perform() # (x, y)
time.sleep(0.5)
time.sleep(2)
browser.quit()
無頭瀏覽器
沒有視覺化介面的瀏覽器
- phantomjs
- 谷歌無頭瀏覽器(推薦使用)
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
# 建立一個引數物件,用來控制chrome以無介面模式開啟
options = Options()
options.add_argument('--headless')
options.add_argument('--disable-gpu')
# 設定瀏覽器
browser = webdriver.Chrome(executable_path='chromedriver.exe', options=options)
browser.get('https://www.taobao.com/')
print(browser.page_source)
如何規避selenium被監測到的風險?
網站可以根據:window.navigator.webdriver的返回值鑑定是否使用了selenium
- undefind:正常
- true:使用了selenium
from selenium import webdriver
from time import sleep
from selenium.webdriver import ChromeOptions
option = ChromeOptions()
option.add_experimental_option('excludeSwitches', ['enable-automation'])
# 後面是瀏覽器驅動位置,記得前面加r'','r'是防止字元轉義的
browser = webdriver.Chrome(r'chromedriver.exe',options=option)
browser.get('https://www.taobao.com/')
模擬登入12306
from ChaoJiYing import Chaojiying_Client
from selenium import webdriver
from selenium.webdriver import ActionChains
from PIL import Image # 圖片操作模組
import time
def get_code(imgPath, imgType):
chaojiying = Chaojiying_Client('賬號', '賬號', '901814')
im = open(imgPath, 'rb').read()
return chaojiying.PostPic(im, imgType)['pic_str']
browser = webdriver.Chrome(executable_path='chromedriver.exe')
browser.get('https://kyfw.12306.cn/otn/login/init')
time.sleep(2)
# 輸入使用者名稱和密碼
username_input = browser.find_element_by_id('username')
username_input.send_keys('賬號')
password_input = browser.find_element_by_id('password')
password_input.send_keys('賬號')
# 截圖
browser.save_screenshot('main.png')
# 在main.png中擷取下驗證碼圖片
img_tag = browser.find_element_by_xpath('//*[@id="loginForm"]/div/ul[2]/li[4]/div/div/div[3]/img')
# 標籤的大小{'height': 190, 'width': 293}
size = img_tag.size
# 標籤左下角的座標{'x': 276, 'y': 274}
location = img_tag.location
# 裁剪範圍
range_xy = (
int(location['x']), int(location['y']), int(location['x'] + size['width']), int(location['y'] + size['height']))
# 必須是png格式
main_png = Image.open(r'main.png')
code_png = main_png.crop(range_xy)
code_png.save('code.png')
# 105,167|105,267|125,167
code = get_code('./code.png', 9004)
# [[105, 167], [105, 267], [125, 167]]
all_list = []
if '|' in code:
list_1 = code.split('|')
count_1 = len(list_1)
for i in range(count_1):
xy_list = []
x = int(list_1[i].split(',')[0])
y = int(list_1[i].split(',')[1])
xy_list.append(x)
xy_list.append(y)
all_list.append(xy_list)
else:
x = int(code.split(',')[0])
y = int(code.split(',')[1])
xy_list = []
xy_list.append(x)
xy_list.append(y)
all_list.append(xy_list)
for x, y in all_list:
# move_to_element_with_offset以標籤的左下角為起點進行偏移
ActionChains(browser).move_to_element_with_offset(img_tag, x, y).click().perform()
time.sleep(1)
# 點選登入
loginSub = browser.find_element_by_id('loginSub')
loginSub.click()