Mục lục
- Ứng dụng nâng cao của module requests
- Giải quyết vấn đề HttpConnectionPool
- Sử dụng proxy IP
- Cách sử dụng proxy đơn giản
- Sử dụng proxy trong mã crawler
- Thiết lập proxy trên trình duyệt
- Tạo pool proxy
- Tác dụng của pool proxy
- Triển khai đơn giản một pool proxy
- Xây dựng một pool proxy
- Xử lý cookie
- Thu thập thông tin tin tức từ trang chủ của Xueqiu https://xueqiu.com/
- Vấn đề gặp phải trong quá trình thu thập
- Giải pháp thêm cookie thủ công (không khuyến khích vì cookie có thể thay đổi)
- Tự động lấy cookie (khuyến khích, hoạt động ngay cả khi cookie thay đổi)
- Cách thêm cookie
- Nhận diện mã xác thực trên trang
- Nhận diện mã xác thực từ trang https://so.gushiwen.org/user/login.aspx?from=http://so.gushiwen.org/user/collect.aspx
- Giải pháp
- Quá trình thực hiện
- Nhận diện mã xác thực trên trang web
- Mô phỏng đăng nhập
- Sử dụng multiprocessing.dummy Pool cho luồng
- Mô phỏng yêu cầu
- Sử dụng Flask đơn giản để mô phỏng server cho testing
- Đơn luồng + đa tác vụ bất đồng bộ với coroutine
- Hiểu đơn giản về hàm coroutine bất đồng bộ asyncio
- Coroutine + đa tác vụ (mô phỏng yêu cầu)
- Sử dụng module requests, không thể thực hiện bất đồng bộ
- Sử dụng module aiohttp, thực hiện được bất đồng bộ
- Ví dụ thứ hai
- Tổng kết
Ứng dụng nâng cao của module requests
Giải quyết vấn đề HttpConnectionPool
- HttpConnectionPool:
- Nguyên nhân:
- 1. Gửi yêu cầu với tần suất cao trong thời gian ngắn dẫn đến IP bị chặn
- 2. Tài nguyên kết nối trong pool HTTP bị cạn kiệt
- Giải pháp:
- 1. Sử dụng proxy
- 2. Thêm Connection: "close" vào headers
Sử dụng proxy IP
- Proxy: Máy chủ proxy có thể nhận yêu cầu và chuyển tiếp nó.
- Mức độ ẩn danh
- Cao ẩn danh: Bên nhận không biết gì
- Ẩn danh: Bên nhận biết bạn đang sử dụng proxy nhưng không biết IP thực của bạn
- Trong suốt: Bên nhận biết bạn đang sử dụng proxy và biết IP thực của bạn
- Loại:
- http
- https
- Proxy miễn phí:
- Proxy IP toàn mạng www.goubanjia.com
- Proxy nhanh https://www.kuaidaili.com/
- Proxy Xici https://www.xicidaili.com/nn/
- Proxy tinh http://http.zhiliandaili.cn/
Cách sử dụng proxy đơn giản
- Máy chủ proxy
- Chuyển tiếp yêu cầu
- Proxy ip:port được áp dụng vào phương thức get, post với proxies = {'http':'ip:port'}
- Pool proxy (danh sách)
Sử dụng proxy trong mã crawler
import requests
headers = {
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36'
}
url = 'https://www.baidu.com/s?wd=ip'
page_text = requests.get(url,headers=headers,proxies={'https':'36.111.140.6:8080'}).text
with open('ip.html','w',encoding='utf-8') as fp:
fp.write(page_text)
Thiết lập proxy trên trình duyệt
Tạo pool proxy
Tác dụng của pool proxy
Giải quyết tình trạng bị chặn IP khi thu thập dữ liệu từ cùng một trang web với tần suất cao trong thời gian ngắn. Cơ chế hoạt động cụ thể: Thu thập IP proxy miễn phí từ các trang web proxy, loại bỏ trùng lặp và lưu trữ vào Redis theo cấu trúc tập hợp có thứ tự, định kỳ kiểm tra tính hiệu quả của IP, thay đổi mức ưu tiên theo quy tắc điểm số tự định nghĩa và xóa các IP có điểm số bằng 0 (vô hiệu). Cung cấp giao diện proxy cho các công cụ crawler sử dụng.
Triển khai đơn giản một pool proxy
# Pool proxy: danh sách
import random
# Từ điển là các proxy ip được tìm trên mạng
proxy_list = [
{'https':'121.231.94.44:8888'},
{'https':'131.231.94.44:8888'},
{'https':'141.231.94.44:8888'}
]
# Chỉ định url
url = 'https://www.baidu.com/s?wd=ip'
# proxies=random.choice(proxy_list) Sử dụng pool proxy
page_text = requests.get(url,headers=headers,proxies=random.choice(proxy_list)).text
with open('ip.html','w',encoding='utf-8') as fp:
fp.write(page_text)
Xây dựng một pool proxy
import random
import requests
from lxml import etree
headers = {
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36',
'Connection':"close"
}
# Trích xuất proxy ip từ proxy精灵
ip_url = 'http://t.11jsq.com/index.php/api/entry?method=proxyServer.generate_api_url&packid=1&fa=0&fetch_key=&groupid=0&qty=4&time=1&pro=&city=&port=1&format=html&ss=5&css=&dt=1&specialTxt=3&specialJson=&usertype=2'
page_text = requests.get(ip_url,headers=headers).text
tree = etree.HTML(page_text)
ip_list = tree.xpath('//body//text()')
# Thu thập từ Xici proxy
url = 'https://www.xicidaili.com/nn/%d'
proxy_list_http = []
proxy_list_https = []
for page in range(1,20):
new_url = format(url%page)
ip_port = random.choice(ip_list)
page_text = requests.get(new_url,headers=headers,proxies={'https':ip_port}).text
tree = etree.HTML(page_text)
# tbody không xuất hiện trong biểu thức xpath
tr_list = tree.xpath('//*[@id="ip_list"]//tr')[1:]
for tr in tr_list:
ip = tr.xpath('./td[2]/text()')[0]
port = tr.xpath('./td[3]/text()')[0]
t_type = tr.xpath('./td[6]/text()')[0]
ips = ip+':'+port
if t_type == 'HTTP':
dic = {
t_type: ips
}
proxy_list_http.append(dic)
else:
dic = {
t_type:ips
}
proxy_list_https.append(dic)
print(len(proxy_list_http),len(proxy_list_https))
# Kiểm tra (ở đây có thể lưu trữ vĩnh viễn)
for ip in proxy_list_http:
response = requests.get('https://www/sogou.com',headers=headers,proxies={'https':ip})
if response.status_code == '200':
print('Đã phát hiện ip khả dụng')
Xử lý cookie
Xử lý thủ công: Đóng gói cookie vào headers
Xử lý tự động: Đối tượng session. Có thể tạo một đối tượng session, đối tượng này có thể gửi yêu cầu giống như requests.
Điểm khác biệt là nếu trong quá trình gửi yêu cầu bằng session tạo ra cookie, cookie sẽ được tự động lưu trữ trong đối tượng session.
Thu thập thông tin tin tức từ trang chủ của Xueqiu https://xueqiu.com/
Vấn đề gặp phải trong quá trình thu thập
import requests
headers = {
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36',
}
url = 'https://xueqiu.com/v4/statuses/public_timeline_by_category.json?since_id=-1&max_id=20349203&count=15&category=-1'
page_text = requests.get(url=url,headers=headers).json()
print(page_text)
# Kết quả thực hiện
{'error_description': 'Gặp lỗi, vui lòng làm mới trang hoặc đăng nhập lại tài khoản', 'error_uri': '/v4/statuses/public_timeline_by_category.json', 'error_data': None, 'error_code': '400016'}
# Phân tích phát hiện, yêu cầu từ trình duyệt thông thường đều mang theo dữ liệu cookie
Giải pháp thêm cookie thủ công (không khuyến khích vì cookie có thể thay đổi)
# Thu thập dữ liệu tin từ Xueqiu https://xueqiu.com/
import requests
headers = {
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36',
'Cookie':'aliyungf_tc=AQAAAAl2aA+kKgkAtxdwe3JmsY226Y+n; acw_tc=2760822915681668126047128e605abf3a5518432dc7f074b2c9cb26d0aa94; xq_a_token=75661393f1556aa7f900df4dc91059df49b83145; xq_r_token=29fe5e93ec0b24974bdd382ffb61d026d8350d7d; u=121568166816578; device_id=24700f9f1986800ab4fcc880530dd0ed',
}
url = 'https://xueqiu.com/v4/statuses/public_timeline_by_category.json?since_id=-1&max_id=20349203&count=15&category=-1'
page_text = requests.get(url=url,headers=headers).json()
print(page_text)
# Kết quả thực hiện
{'list': [{'id': 20349202, 'category': 0, 'data': '{"id":132614531,"title":"Sói đã đến! Hôm nay, ngành viễn thông Trung Quốc bắn phát súng đầu tiên! Chi phí lưu lượng sẽ giảm giá!","description":"Sói, cuối cùng cũng đã đến! Vừa mới có tin lớn, Bộ Công nghiệp và Công nghệ thông tin Trung Quốc chính thức tuyên bố: British Telecom (BT) đã nhận được giấy phép kinh doanh liên lạc trên toàn quốc tại Trung Quốc. Sau đó, British Telecom cũng xác nhận tin tức này ngay lập tức! Họ hớn hở cho biết: nhận được giấy phép, có nghĩa là British Telecom đã bước đi quan trọng tại Trung Quốc! Đúng vậy, bạn không nhìn nhầm: British Telecom! Đây là công ty viễn thông lớn nhất của Anh, cũng là một công ty có lịch sử trên...","target":"/3583653389/132614531","reply_count":75,"retweet_count":7,"topic_title":"Sói đã đến! Hôm nay, ngành viễn thông Trung Quốc bắn phát súng đầu tiên! Chi phí lưu lượng sẽ giảm giá!","topic_desc":"Sói, cuối cùng cũng đã đến! Vừa mới có tin lớn, Bộ Công nghiệp và Công nghệ thông tin Trung Quốc chính thức tuyên bố: Anh...}.....bỏ qua
Tự động lấy cookie (khuyến khích, hoạt động ngay cả khi cookie thay đổi)
import requests
# Tạo đối tượng session
session = requests.Session()
headers = {
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36',
}
session.get('https://xueqiu.com',headers=headers)# Tự động mang theo thông tin cookie trong yêu cầu
url = 'https://xueqiu.com/v4/statuses/public_timeline_by_category.json?since_id=-1&max_id=20349203&count=15&category=-1'
page_text = session.get(url=url,headers=headers).json()
print(page_text)
# Kết quả thực hiện
{'list': [{'id': 20349202, 'category': 0, 'data': '{"id":132614531,"title":"Sói đã đến! Hôm nay, ngành viễn thông Trung Quốc bắn phát súng đầu tiên! Chi phí lưu lượng sẽ giảm giá!","description":"Sói, cuối cùng cũng đã đến! Vừa mới có tin lớn, Bộ Công nghiệp và Công nghệ thông tin Trung Quốc chính thức tuyên bố: British Telecom (BT) đã nhận được giấy phép kinh doanh liên lạc trên toàn quốc tại Trung Quốc. Sau đó, British Telecom cũng xác nhận tin tức này ngay lập tức! Họ hớn hở cho biết: nhận được giấy phép, có nghĩa là British Telecom đã bước đi quan trọng tại Trung Quốc! Đúng vậy, bạn không nhìn nhầm: British Telecom! Đây là công ty viễn thông lớn nhất của Anh, cũng là một công ty có lịch sử trên...","target":"/3583653389/132614531","reply_count":75,"retweet_count":7,"topic_title":"Sói đã đến! Hôm nay, ngành viễn thông Trung Quốc bắn phát súng đầu tiên! Chi phí lưu lượng sẽ giảm giá!","topic_desc":"Sói, cuối cùng cũng đã đến! Vừa mới có tin lớn, Bộ Công nghiệp và Công nghệ thông tin Trung Quốc chính thức tuyên bố: Anh...}......bỏ qua
Cách thêm cookie
Trường hợp thông thường sử dụng requests.Session() có thể giải quyết vấn đề cookies, nhưng trong quá trình thêm cookie gặp một số vấn đề.
Loại 1:
session = requests.Session()
session.cookies['cookie'] = 'cookie-value'
Chức năng: Có thể thêm cookie, không xóa cookie cũ
Nhược điểm: Không thể thiết lập path, domain và các tham số khác
Loại 2:
session = requests.Session()
session.cookies.set('cookie-name', 'cookie-value', path='/', domain='.abc.com')
Chức năng: Thiết lập path, domain và các tham số khác.
Nhược điểm: Xóa các cookies cũ
Loại 3:
session = requests.Session()
requests.utils.add_dict_to_cookiejar(session.cookies, cookie_dict)
Chức năng: Có thể thêm cookie, không xóa cookie cũ
Nhược điểm: Không thể thiết lập path, domain và các tham số khác
Loại 4:(có vấn đề cần giải quyết)
session = requests.Session()
c = requests.cookies.RequestsCookieJar()
c.set('cookie-name', 'cookie-value', path='/', domain='.abc.com')
session.cookies.update(c)
Chức năng: Vừa có thể thêm cookies, vừa có thể thêm path, domain và các tham số khác.
Loại 5:
session = requests.Session()
session.get(url='www.xxx.com')
headers = {
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,
'Connection': 'keep-alive',
'Host': 'www.airchina.com.cn',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36',
}
cookie_str = 'cookie1=xxxx1;cookie=xxxx2'
headers['Cookie'] = cookie_str
session.get(url = 'www.xxx2.com',headers=headers)
Chức năng: Cả cookie của session và cookie của headers đều có hiệu lực, nhưng chỉ có hiệu lực khi sử dụng headers này
Nhận diện mã xác thực trên trang
Nhận diện mã xác thực từ trang https://so.gushiwen.org/user/login.aspx?from=http://so.gushiwen.org/user/collect.aspx
Giải pháp
Nền tảng nhận diện mã xác thực được khuyến nghị
- Siêu Ưng: http://www.chaojiying.com/about.html (sử dụng Siêu Ưng ở đây)
- Đăng ký:(Trung tâm người dùng xác thực)
- Đăng nhập:
- Tạo một phần mềm: 899370
- Tải xuống mã mẫu
- Cloud Code:http://www.yundama.com/
Quá trình thực hiện
Nhận diện mã xác thực trên trang web
# Mã Siêu Ưng
import requests
from hashlib import md5
class Chaojiying_Client(object):
def __init__(self, username, password, soft_id):
self.username = username
password = password.encode('utf8')
self.password = md5(password).hexdigest()
self.soft_id = soft_id
self.base_params = {
'user': self.username,
'pass2': self.password,
'softid': self.soft_id,
}
self.headers = {
'Connection': 'Keep-Alive',
'User-Agent': 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0)',
}
def PostPic(self, im, codetype):
"""
im: Byte ảnh
codetype: Loại câu hỏi tham khảo http://www.chaojiying.com/price.html
"""
params = {
'codetype': codetype,
}
params.update(self.base_params)
files = {'userfile': ('ccc.jpg', im)}
r = requests.post('http://upload.chaojiying.net/Upload/Processing.php', data=params, files=files, headers=self.headers)
return r.json()
def ReportError(self, im_id):
"""
im_id: ID ảnh của câu hỏi báo lỗi
"""
params = {
'id': im_id,
}
params.update(self.base_params)
r = requests.post('http://upload.chaojiying.net/Upload/ReportError.php', data=params, headers=self.headers)
return r.json()
# Mã crawler
# Nhận diện mã xác thực từ trang cổ thi văn
from lxml import etree
# Nhận diện mã xác thực từ trang cổ thi văn
def tranformImgData(imgPath,t_type):# Gọi Siêu Ưng
chaojiying = Chaojiying_Client('bobo3280948', 'bobo3284148', '899370')# Tài khoản Siêu Ưng Mật khẩu ID phần mềm
im = open(imgPath, 'rb').read()
return chaojiying.PostPic(im, t_type)['pic_str']
headers = {
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36',
}
url = 'https://so.gushiwen.org/user/login.aspx?from=http://so.gushiwen.org/user/collect.aspx'
page_text = requests.get(url,headers=headers).text
tree = etree.HTML(page_text)
img_src = 'https://so.gushiwen.org'+tree.xpath('//*[@id="imgCode"]/@src')[0]
img_data = requests.get(img_src,headers=headers).content
with open('./code.jpg','wb') as fp:
fp.write(img_data)
yzm = tranformImgData('./code.jpg',1004)# Đường dẫn ảnh captcha lưu trữ Số loại captcha tương ứng với Siêu Ưng
print(yzm)
# Kết quả thực hiện Giải mã captcha thành công
d145
Mô phỏng đăng nhập
import requests
from hashlib import md5
class Chaojiying_Client(object):
def __init__(self, username, password, soft_id):
self.username = username
password = password.encode('utf8')
self.password = md5(password).hexdigest()
self.soft_id = soft_id
self.base_params = {
'user': self.username,
'pass2': self.password,
'softid': self.soft_id,
}
self.headers = {
'Connection': 'Keep-Alive',
'User-Agent': 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0)',
}
def PostPic(self, im, codetype):
"""
im: Byte ảnh
codetype: Loại câu hỏi tham khảo http://www.chaojiying.com/price.html
"""
params = {
'codetype': codetype,
}
params.update(self.base_params)
files = {'userfile': ('ccc.jpg', im)}
r = requests.post('http://upload.chaojiying.net/Upload/Processing.php', data=params, files=files, headers=self.headers)
return r.json()
def ReportError(self, im_id):
"""
im_id: ID ảnh của câu hỏi báo lỗi
"""
params = {
'id': im_id,
}
params.update(self.base_params)
r = requests.post('http://upload.chaojiying.net/Upload/ReportError.php', data=params, headers=self.headers)
return r.json()
from lxml import etree
# Nhận diện mã xác thực từ trang cổ thi văn
def tranformImgData(imgPath,t_type):# Gọi Siêu Ưng
chaojiying = Chaojiying_Client('bobo328410948', 'bobo328410948', '899370')
im = open(imgPath, 'rb').read()
return chaojiying.PostPic(im, t_type)['pic_str']
headers = {
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36',
}
# Mô phỏng đăng nhập
s = requests.Session()
url = 'https://so.gushiwen.org/user/login.aspx?from=http://so.gushiwen.org/user/collect.aspx'
page_text = s.get(url,headers=headers).text
tree = etree.HTML(page_text)
img_src = 'https://so.gushiwen.org'+tree.xpath('//*[@id="imgCode"]/@src')[0]
img_data = s.get(img_src,headers=headers).content
with open('./code.jpg','wb') as fp:
fp.write(img_data)
# Lấy động các tham số yêu cầu thay đổi
__VIEWSTATE = tree.xpath('//*[@id="__VIEWSTATE"]/@value')[0]
__VIEWSTATEGENERATOR = tree.xpath('//*[@id="__VIEWSTATEGENERATOR"]/@value')[0]
code_text = tranformImgData('./code.jpg',1004)
login_url = 'https://so.gushiwen.org/user/login.aspx?from=http%3a%2f%2fso.gushiwen.org%2fuser%2fcollect.aspx'
data = {
'__VIEWSTATE': __VIEWSTATE,
'__VIEWSTATEGENERATOR': __VIEWSTATEGENERATOR,
'from':'http://so.gushiwen.org/user/collect.aspx',
'email': 'www.zhangbowudi@qq.com',
'pwd': 'bobo328410948',
'code': code_text,
'denglu': 'Đăng nhập',
}
page_text = s.post(url=login_url,headers=headers,data=data).text
with open('login.html','w',encoding='utf-8') as fp:
fp.write(page_text)
# Các tham số yêu cầu thay đổi Thông thường các tham số yêu cầu thay đổi động sẽ được ẩn trong mã nguồn của trang trước đó
Sử dụng multiprocessing.dummy Pool cho luồng
Mô phỏng yêu cầu
# Không sử dụng pool luồng (mô phỏng yêu cầu)
import time
from time import sleep
start = time.time()
urls = [
'www.1.com',
'www.2.com',
'www.3.com',
]
def get_request(url):
print('Đang truy cập:%s'%url)
sleep(2)
print('Kết thúc truy cập:%s'%url)
for url in urls:
get_request(url)
print('Tổng thời gian:',time.time()-start)
# Kết quả thực hiện
Đang truy cập:www.1.com
Kết thúc truy cập:www.1.com
Đang truy cập:www.2.com
Kết thúc truy cập:www.2.com
Đang truy cập:www.3.com
Kết thúc truy cập:www.3.com
Tổng thời gian: 6.000494718551636
# Sử dụng pool luồng (mô phỏng yêu cầu)
import time
from time import sleep
from multiprocessing.dummy import Pool
start = time.time()
urls = [
'www.1.com',
'www.2.com',
'www.3.com',
]
def get_request(url):
print('Đang truy cập:%s' % url)
sleep(2)
print('Kết thúc truy cập:%s' % url)
pool = Pool(3)
pool.map(get_request, urls)
print('Tổng thời gian:', time.time() - start)
# Kết quả thực hiện
Đang truy cập:www.1.com
Đang truy cập:www.2.com
Đang truy cập:www.3.com
Kết thúc truy cập:www.2.com
Kết thúc truy cập:www.3.com
Kết thúc truy cập:www.1.com
Tổng thời gian: 2.037109613418579
Sử dụng Flask đơn giản để mô phỏng server cho testing
# Server
from flask import Flask
from time import sleep
app = Flask(__name__)
@app.route('/index')
def index():
sleep(2)
return 'xin chào'
if __name__ == '__main__':
app.run()
# Mã yêu cầu crawler
import time
import requests
from multiprocessing.dummy import Pool
start = time.time()
urls = [
'http://localhost:5000/index',
'http://localhost:5000/index',
'http://localhost:5000/index',
]
def get_request(url):
page_text = requests.get(url).text
print(page_text)
pool = Pool(3)
pool.map(get_request, urls)
print('Tổng thời gian:', time.time() - start)
# Kết quả thực hiện
xin chào
xin chào
xin chào
Tổng thời gian: 3.0322463512420654
Đơn luồng + đa tác vụ bất đồng bộ với coroutine
- Coroutine
- Khi định nghĩa hàm (đặc biệt), nếu sử dụng async để sửa đổi, thì sau khi gọi hàm sẽ trả về một đối tượng coroutine, và câu lệnh thực hiện bên trong hàm sẽ không được thực thi ngay lập tức
- Đối tượng tác vụ
- Đối tượng tác vụ là sự đóng gói thêm của đối tượng coroutine. Đối tượng tác vụ = đối tượng coroutine nâng cao = hàm đặc biệt
- Đối tượng tác vụ phải được đăng ký vào đối tượng vòng lặp sự kiện
- Liên kết hàm callback cho đối tượng tác vụ: trong phân tích dữ liệu crawler
- Vòng lặp sự kiện
- Coi như một container, container này phải chứa đối tượng tác vụ.
- Khi khởi động đối tượng vòng lặp sự kiện, đối tượng này sẽ thực hiện bất đồng bộ các đối tượng tác vụ được lưu trữ bên trong.
- aiohttp: Module hỗ trợ yêu cầu mạng bất đồng bộ
Hiểu đơn giản về hàm coroutine bất đồng bộ asyncio
import asyncio
def callback(task):# Làm hàm callback cho đối tượng tác vụ
print('Tôi là callback và ',task.result())# task.result() nhận giá trị trả về của hàm đặc biệt
async def test(): # Hàm đặc biệt
print('Tôi là test()')
return 'bobo'
c = test()# c là đối tượng coroutine
# Đóng gói một đối tượng tác vụ
task = asyncio.ensure_future(c)
# Liên kết hàm callback
task.add_done_callback(callback)
# Tạo một đối tượng vòng lặp sự kiện
loop = asyncio.get_event_loop()
# Đăng ký đối tượng tác vụ vào vòng lặp sự kiện
loop.run_until_complete(task)
# Kết quả thực hiện
Tôi là test()
Tôi là callback và bobo
Coroutine + đa tác vụ (mô phỏng yêu cầu)
import time
import asyncio
start = time.time()
# Trong hàm đặc biệt, không thể có mã của module không hỗ trợ bất đồng bộ
async def get_request(url):
await asyncio.sleep(2)
print('Truy cập thành công:', url)
urls = [
'www.1.com',
'www.2.com'
]
tasks = []
for url in urls:
c = get_request(url)
task = asyncio.ensure_future(c)
tasks.append(task)
loop = asyncio.get_event_loop()
# Lưu ý: Vận hành treo cần xử lý thủ công
loop.run_until_complete(asyncio.wait(tasks))
print(time.time() - start)
# Kết quả thực hiện
Truy cập thành công: www.1.com
Truy cập thành công: www.2.com
2.002183198928833
Sử dụng module requests, không thể thực hiện bất đồng bộ
# Server
from flask import Flask
from time import sleep
app = Flask(__name__)
@app.route('/index')
def index():
sleep(2)
return 'xin chào'
@app.route('/index1')
def index1():
sleep(2)
return 'xin chào1'
if __name__ == '__main__':
app.run()
# Mã crawler
import requests
import time
import asyncio
s = time.time()
urls = [
'http://127.0.0.1:5000/index',
'http://127.0.0.1:5000/home'
]
async def get_request(url):
page_text = requests.get(url).text
return page_text
tasks = []
for url in urls:
c = get_request(url)
task = asyncio.ensure_future(c)
tasks.append(task)
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))
print(time.time()-s)
# Kết quả thực hiện Không thực hiện được bất đồng bộ
4.021323204040527
# Vì requests không hỗ trợ bất đồng bộ, cần sử dụng aiohttp
Sử dụng module aiohttp, thực hiện được bất đồng bộ
# Server
from flask import Flask
from time import sleep
app = Flask(__name__)
@app.route('/index')
def index():
sleep(2)
return 'chỉ mục'
@app.route('/home')
def index1():
sleep(2)
return 'trang chủ'
if __name__ == '__main__':
app.run()
# Mã crawler
import aiohttp
import time
import asyncio
s = time.time()
urls = [
'http://127.0.0.1:5000/index',
'http://127.0.0.1:5000/home'
]
async def get_request(url):
# Mỗi with trước cần thêm async
async with aiohttp.ClientSession() as s:
# Thêm await trước hoạt động chặn
async with await s.get(url=url) as response:#get(url=url,headers,params,proxy) các tham số có thể sử dụng
page_text = await response.text()# Cần thêm dấu ngoặc đơn, là phương pháp
print(page_text)
return page_text
tasks = []
for url in urls:
c = get_request(url)
task = asyncio.ensure_future(c)
tasks.append(task)
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))
print(time.time() - s)
# Kết quả thực hiện
chỉ mục
trang chủ
2.016155242919922
Ví dụ thứ hai
######################## Tệp test.html ########################
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- 3 thẻ meta trên*phải* đặt ở đầu, bất kỳ nội dung nào khác*phải* đi theo sau! -->
<title>Mẫu Bootstrap 101</title>
<!-- Bootstrap -->
<link href="bootstrap-3.3.7-dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<h1>Xin chào thế giới!</h1>
<ul>
<li>tôi là anh hùng!!!</li>
<li>tôi là siêu nhân!!!</li>
<li>tôi là nhện!!!</li>
</ul>
</body>
</html>
######################## Server ########################
import time
from flask import Flask,render_template
app = Flask(__name__)
@app.route('/bobo')
def index_bobo():
time.sleep(2)
return render_template('test.html')
@app.route('/jay')
def index_jay():
time.sleep(2)
return render_template('test.html')
@app.route('/tom')
def index_tom():
time.sleep(2)
return render_template('test.html')
if __name__ == '__main__':
app.run(threaded=True)
######################## Mã crawler ########################
import time
import aiohttp
import asyncio
from lxml import etree
start = time.time()
urls = [
'http://127.0.0.1:5000/bobo',
'http://127.0.0.1:5000/jay',
'http://127.0.0.1:5000/tom',
'http://127.0.0.1:5000/bobo',
'http://127.0.0.1:5000/jay',
'http://127.0.0.1:5000/tom',
'http://127.0.0.1:5000/bobo',
'http://127.0.0.1:5000/jay',
'http://127.0.0.1:5000/tom',
'http://127.0.0.1:5000/bobo',
'http://127.0.0.1:5000/jay',
'http://127.0.0.1:5000/tom'
]
# Hàm đặc biệt: gửi yêu cầu và nắm bắt dữ liệu phản hồi
# Chi tiết: Thêm async trước mỗi with, thêm await trước mỗi hoạt động chặn
async def get_request(url):
async with aiohttp.ClientSession() as s:
# s.get(url,headers,proxy="http://ip:port",params)
async with await s.get(url) as response:
page_text = await response.text() # read() trả về dữ liệu kiểu byte
return page_text
# Hàm callback
def parse(task):
page_text = task.result()
tree = etree.HTML(page_text)
parse_data = tree.xpath('//li/text()')
print(parse_data)
tasks = []
for url in urls:
c = get_request(url)
task = asyncio.ensure_future(c)
task.add_done_callback(parse)
tasks.append(task)
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))
print(time.time() - start)
# Kết quả thực hiện Thực hiện được bất đồng bộ
['tôi là anh hùng!!!', 'tôi là siêu nhân!!!', 'tôi là nhện!!!']
['tôi là anh hùng!!!', 'tôi là siêu nhân!!!', 'tôi là nhện!!!']
['tôi là anh hùng!!!', 'tôi là siêu nhân!!!', 'tôi là nhện!!!']
['tôi là anh hùng!!!', 'tôi là siêu nhân!!!', 'tôi là nhện!!!']
['tôi là anh hùng!!!', 'tôi là siêu nhân!!!', 'tôi là nhện!!!']
['tôi là anh hùng!!!', 'tôi là siêu nhân!!!', 'tôi là nhện!!!']
['tôi là anh hùng!!!', 'tôi là siêu nhân!!!', 'tôi là nhện!!!']
['tôi là anh hùng!!!', 'tôi là siêu nhân!!!', 'tôi là nhện!!!']
['tôi là anh hùng!!!', 'tôi là siêu nhân!!!', 'tôi là nhện!!!']
['tôi là anh hùng!!!', 'tôi là siêu nhân!!!', 'tôi là nhện!!!']
['tôi là anh hùng!!!', 'tôi là siêu nhân!!!', 'tôi là nhện!!!']
['tôi là anh hùng!!!', 'tôi là siêu nhân!!!', 'tôi là nhện!!!']
2.094982147216797
Tổng kết
- Đơn luồng + đa tác vụ bất đồng bộ với coroutine
- Coroutine
- Nếu một hàm được sửa đổi bằng async, thì sau khi gọi hàm sẽ trả về một đối tượng coroutine.
- Đối tượng tác vụ:
- Là sự đóng gói thêm của đối tượng coroutine
- Liên kết callback
- task.add_done_callback(func):func(task):task.result()
- Đối tượng vòng lặp sự kiện
- Đối tượng vòng lặp sự kiện được dùng để chứa các đối tượng tác vụ. Khi đối tượng này được khởi động, nó sẽ xử lý bất đồng bộ từng đối tượng tác vụ được chứa bên trong.(Thực hiện thao tác treo thủ công cho các đối tượng tác vụ)
- aynic, await
- Lưu ý: Trong hàm đặc biệt không thể có mã của module không hỗ trợ bất đồng bộ, nếu không sẽ gián đoạn toàn bộ hiệu quả bất đồng bộ!!!
- aiohttp: Module hỗ trợ yêu cầu bất đồng bộ