Làm thế nào tôi có thể cạo nhanh hơn


16

Công việc ở đây là để cạo một API một trang web bắt đầu từ https://xxx.xxx.xxx/xxx/1.jsonđến https://xxx.xxx.xxx/xxx/1417749.jsonvà viết nó một cách chính xác để MongoDB. Cho rằng tôi có mã sau đây:

client = pymongo.MongoClient("mongodb://127.0.0.1:27017")
db = client["thread1"]
com = db["threadcol"]
start_time = time.time()
write_log = open("logging.log", "a")
min = 1
max = 1417749
for n in range(min, max):
    response = requests.get("https:/xx.xxx.xxx/{}.json".format(str(n)))
    if response.status_code == 200:
        parsed = json.loads(response.text)
        inserted = com.insert_one(parsed)
        write_log.write(str(n) + "\t" + str(inserted) + "\n")
        print(str(n) + "\t" + str(inserted) + "\n")
write_log.close()

Nhưng nó đang mất rất nhiều thời gian để thực hiện nhiệm vụ. Câu hỏi ở đây là làm thế nào tôi có thể tăng tốc quá trình này.


Lần đầu tiên bạn thử điểm chuẩn mất bao lâu để xử lý json đơn? Giả sử phải mất 300ms cho mỗi bản ghi, bạn có thể xử lý tất cả các bản ghi này một cách tuần tự trong khoảng 5 ngày.
tuxdna

Câu trả lời:


5

asyncio cũng là một giải pháp nếu bạn không muốn sử dụng đa luồng

import time
import pymongo
import json
import asyncio
from aiohttp import ClientSession


async def get_url(url, session):
    async with session.get(url) as response:
        if response.status == 200:
            return await response.text()


async def create_task(sem, url, session):
    async with sem:
        response = await get_url(url, session)
        if response:
            parsed = json.loads(response)
            n = url.rsplit('/', 1)[1]
            inserted = com.insert_one(parsed)
            write_log.write(str(n) + "\t" + str(inserted) + "\n")
            print(str(n) + "\t" + str(inserted) + "\n")


async def run(minimum, maximum):
    url = 'https:/xx.xxx.xxx/{}.json'
    tasks = []
    sem = asyncio.Semaphore(1000)   # Maximize the concurrent sessions to 1000, stay below the max open sockets allowed
    async with ClientSession() as session:
        for n in range(minimum, maximum):
            task = asyncio.ensure_future(create_task(sem, url.format(n), session))
            tasks.append(task)
        responses = asyncio.gather(*tasks)
        await responses


client = pymongo.MongoClient("mongodb://127.0.0.1:27017")
db = client["thread1"]
com = db["threadcol"]
start_time = time.time()
write_log = open("logging.log", "a")
min_item = 1
max_item = 100

loop = asyncio.get_event_loop()
future = asyncio.ensure_future(run(min_item, max_item))
loop.run_until_complete(future)
write_log.close()

1
Sử dụng async hoạt động nhanh hơn so với đa luồng.
Tek Nath

Cảm ơn vì bạn đã phản hồi. Kết quả thú vị.
Frans

10

Có một số điều bạn có thể làm:

  1. Tái sử dụng kết nối. Theo điểm chuẩn dưới đây thì nhanh hơn khoảng 3 lần
  2. Bạn có thể cạo song song trong nhiều quy trình

Mã song song từ đây

from threading import Thread
from Queue import Queue
q = Queue(concurrent * 2)
for i in range(concurrent):
    t = Thread(target=doWork)
    t.daemon = True
    t.start()
try:
    for url in open('urllist.txt'):
        q.put(url.strip())
    q.join()
except KeyboardInterrupt:
    sys.exit(1)

Thời gian từ câu hỏi này cho kết nối có thể sử dụng lại

>>> timeit.timeit('_ = requests.get("https://www.wikipedia.org")', 'import requests', number=100)
Starting new HTTPS connection (1): www.wikipedia.org
Starting new HTTPS connection (1): www.wikipedia.org
Starting new HTTPS connection (1): www.wikipedia.org
...
Starting new HTTPS connection (1): www.wikipedia.org
Starting new HTTPS connection (1): www.wikipedia.org
Starting new HTTPS connection (1): www.wikipedia.org
52.74904417991638
>>> timeit.timeit('_ = session.get("https://www.wikipedia.org")', 'import requests; session = requests.Session()', number=100)
Starting new HTTPS connection (1): www.wikipedia.org
15.770191192626953


4

Những gì bạn có thể đang tìm kiếm là cạo không đồng bộ. Tôi sẽ khuyên bạn nên tạo một số lô url, tức là 5 url (cố gắng không làm hỏng trang web) và cạo chúng không đồng bộ. Nếu bạn không biết nhiều về async, hãy tìm kiếm asyncio miễn phí. Tôi hy vọng tôi có thể giúp bạn :)


1
Bạn có thể thêm một số chi tiết.
Tek Nath

3

Cố gắng xử lý các yêu cầu và sử dụng thao tác ghi số lượng lớn MongoDB.

  • nhóm các yêu cầu (100 yêu cầu mỗi nhóm)
  • Lặp lại qua các nhóm
  • Sử dụng mô hình yêu cầu không đồng bộ để tìm nạp dữ liệu (URL trong một nhóm)
  • Cập nhật DB sau khi hoàn thành một nhóm (Thao tác ghi hàng loạt)

Điều này có thể tiết kiệm rất nhiều thời gian theo các cách sau * Độ trễ ghi MongoDB * độ trễ cuộc gọi mạng đồng bộ

Nhưng không tăng số lượng yêu cầu song song (kích thước Chunk), Nó sẽ tăng tải mạng của máy chủ và máy chủ có thể nghĩ đây là một cuộc tấn công DDoS.

  1. https://api.mongodb.com/python/civerse/examples/bulk.html

1
Bạn có thể giúp với mã để nhóm các yêu cầu và tìm nạp nhóm
Tek Nath

3

Giả sử rằng bạn sẽ không bị chặn bởi API và không có giới hạn tỷ lệ, mã này sẽ giúp quá trình nhanh hơn 50 lần (có thể nhiều hơn vì tất cả các yêu cầu hiện được gửi bằng cùng một phiên).

import pymongo
import threading

client = pymongo.MongoClient("mongodb://127.0.0.1:27017")
db = client["thread1"]
com = db["threadcol"]
start_time = time.time()
logs=[]

number_of_json_objects=1417750
number_of_threads=50

session=requests.session()

def scrap_write_log(session,start,end):
    for n in range(start, end):
        response = session.get("https:/xx.xxx.xxx/{}.json".format(n))
        if response.status_code == 200:
            try:
                logs.append(str(n) + "\t" + str(com.insert_one(json.loads(response.text))) + "\n")
                print(str(n) + "\t" + str(inserted) + "\n")
            except:
                logs.append(str(n) + "\t" + "Failed to insert" + "\n")
                print(str(n) + "\t" + "Failed to insert" + "\n")

thread_ranges=[[x,x+number_of_json_objects//number_of_threads] for x in range(0,number_of_json_objects,number_of_json_objects//number_of_threads)]

threads=[threading.Thread(target=scrap_write_log, args=(session,start_and_end[0],start_and_end[1])) for start_and_end in thread_ranges]

for thread in threads:
    thread.start()
for thread in threads:
    thread.join()

with open("logging.log", "a") as f:
    for line in logs:
        f.write(line)

2

Tôi tình cờ có cùng một câu hỏi nhiều năm trước. Tôi không bao giờ hài lòng với câu trả lời dựa trên python, khá chậm hoặc quá phức tạp. Sau khi tôi chuyển sang các công cụ trưởng thành khác, tốc độ rất nhanh và tôi không bao giờ quay lại.

Gần đây tôi sử dụng các bước như vậy để tăng tốc quá trình như sau.

  1. tạo ra một loạt các url trong txt
  2. sử dụng aria2c -x16 -d ~/Downloads -i /path/to/urls.txtđể tải những tập tin này
  3. phân tích cục bộ

Đây là quá trình nhanh nhất tôi đã đưa ra cho đến nay.

Về mặt cạo các trang web, tôi thậm chí tải xuống cần thiết * .html, thay vì truy cập trang một lần, điều này thực sự không có gì khác biệt. Khi bạn nhấn truy cập trang, với các công cụ python như requestshay scrapyhay urllib, nó vẫn nhớ cache và tải về nội dung web toàn cho bạn.


1

Đầu tiên tạo danh sách tất cả các liên kết vì tất cả đều giống nhau chỉ thay đổi lặp lại nó.

list_of_links=[]
for i in range(1,1417749):
    list_of_links.append("https:/xx.xxx.xxx/{}.json".format(str(i)))

t_no=2
for i in range(0, len(list_of_links), t_no):
    all_t = []
    twenty_links = list_of_links[i:i + t_no]
    for link in twenty_links:
        obj_new = Demo(link,)
        t = threading.Thread(target=obj_new.get_json)
        t.start()
        all_t.append(t)
    for t in all_t:
        t.join()

class Demo:
    def __init__(self, url):
        self.json_url = url

def get_json(self):
    try:
       your logic
    except Exception as e:
       print(e)

Chỉ cần tăng hoặc giảm t_no, bạn không thể thay đổi chủ đề ..

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.