Ứng dụng nâng cao của module requests

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ộ

Thẻ: requests proxy Cookie CAPTCHA asynchronous

Đăng vào ngày 26 tháng 6 lúc 06:07