Python urllib2.urlopen() медленный, нужен лучший способ прочитать несколько URL-адресов

Как видно из названия, я работаю над сайтом, написанным на python, и несколько раз звонил в модуль urllib2 для чтения веб-сайтов. Затем я разбираю их с помощью BeautifulSoup.

Как я должен прочитать 5-10 сайтов, страница занимает некоторое время, чтобы загрузить.

Мне просто интересно, есть ли способ читать сайты сразу? Или anytricks, чтобы сделать это быстрее, например, следует закрыть urllib2.urlopen после каждого чтения или оставить его открытым?

Добавлено: также, если бы я просто переключился на php, это было бы быстрее для извлечения и Parsi g файлов HTML и XML с других сайтов? Я просто хочу, чтобы он загружался быстрее, в отличие от ~ 20 секунд, которые он сейчас занимает

Ответ 1

Я переписываю код Dumb Guy ниже, используя современные модули Python, такие как threading и Queue.

import threading, urllib2
import Queue

urls_to_load = [
'http://stackoverflow.com/',
'http://slashdot.org/',
'http://www.archive.org/',
'http://www.yahoo.co.jp/',
]

def read_url(url, queue):
    data = urllib2.urlopen(url).read()
    print('Fetched %s from %s' % (len(data), url))
    queue.put(data)

def fetch_parallel():
    result = Queue.Queue()
    threads = [threading.Thread(target=read_url, args = (url,result)) for url in urls_to_load]
    for t in threads:
        t.start()
    for t in threads:
        t.join()
    return result

def fetch_sequencial():
    result = Queue.Queue()
    for url in urls_to_load:
        read_url(url,result)
    return result

Лучшее время для find_sequencial() равно 2s. Лучшее время для fetch_parallel() составляет 0,9 с.

Также неверно говорить, что thread бесполезен в Python из-за GIL. Это один из тех случаев, когда поток полезен в Python, потому что потоки блокируются при вводе-выводе. Как вы можете видеть в моем результате, параллельный случай в 2 раза быстрее.

Ответ 2

Изменить: Пожалуйста, взгляните на сообщение Wai для лучшей версии этого кода. Обратите внимание, что в этом коде нет ничего плохого, и будет работать правильно, несмотря на комментарии ниже.

Скорость чтения веб-страниц, вероятно, ограничена вашим интернет-соединением, а не Python.

Вы можете использовать потоки, чтобы загрузить их все сразу.

import thread, time, urllib
websites = {}
def read_url(url):
  websites[url] = urllib.open(url).read()

for url in urls_to_load: thread.start_new_thread(read_url, (url,))
while websites.keys() != urls_to_load: time.sleep(0.1)

# Now websites will contain the contents of all the web pages in urls_to_load

Ответ 3

Это мабы не идеально. Но когда мне нужны данные с сайта. Я просто делаю это:

import socket
def geturldata(url):
    #NO HTTP URLS PLEASE!!!!! 
    server = url.split("/")[0]
    args = url.replace(server,"")
    returndata = str()
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect((server, 80)) #lets connect :p

    s.send("GET /%s HTTP/1.0\r\nHost: %s\r\n\r\n" % (args, server)) #simple http request
    while 1:
        data = s.recv(1024) #buffer
        if not data: break
        returndata = returndata + data
    s.close()
    return returndata.split("\n\r")[1]

Ответ 4

Как правило, данная конструкция на любом языке не замедляется до тех пор, пока она не будет измерена.

В Python не только тайминги часто идут вразрез с интуицией, но инструменты для измерения времени выполнения являются исключительно хорошими.

Ответ 5

Scrapy может быть вам полезен. Если вам не нужны все его функции, вы можете просто использовать twisted twisted.web.client.getPage. Асинхронный IO в одном потоке будет более эффективным и легким для отладки, чем все, что использует несколько потоков и блокирует IO.

Ответ 6

Не уверен, почему никто не упоминает multiprocessing (если кто знает, почему это может быть плохой идеей, дайте мне знать):

import multiprocessing
from urllib2 import urlopen

URLS = [....]

def get_content(url):
    return urlopen(url).read()


pool = multiprocessing.Pool(processes=8)  # play with ``processes`` for best results
results = pool.map(get_content, URLS) # This line blocks, look at map_async 
                                      # for non-blocking map() call
pool.close()  # the process pool no longer accepts new tasks
pool.join()   # join the processes: this blocks until all URLs are processed
for result in results:
   # do something

Существует несколько предостережений с пулами multiprocessing. Во-первых, в отличие от потоков, это совершенно новые процессы Python (интерпретатор). Хотя он не подлежит глобальному блокированию интерпретатора, это означает, что вы ограничены тем, что вы можете передать новому процессу.

Вы не можете передавать lambdas и функции, которые определяются динамически. Функция, которая используется в вызове map(), должна быть определена в вашем модуле таким образом, чтобы другой процесс мог импортировать его.

Pool.map(), который является самым простым способом обработки нескольких задач одновременно, не обеспечивает способ передачи нескольких аргументов, поэтому вам может понадобиться писать функции-обертки или изменять сигнатуры функций и/или передавать несколько аргументов как часть повторяемого, который отображается.

У вас нет дочерних процессов, которые порождают новые. Только родитель может порождать дочерние процессы. Это означает, что вам нужно тщательно планировать и тестировать (а иногда и писать несколько версий кода), чтобы определить, насколько наиболее эффективным будет использование процессов.

Несмотря на недостатки, я считаю, что многопроцессорность является одним из самых простых способов совершения параллельных блокирующих вызовов. Вы также можете комбинировать многопроцессорность и потоки (afaik, но, пожалуйста, поправьте меня, если я ошибаюсь), или объедините многопроцессорность с зелеными потоками.

Ответ 7

1) Вы открываете один и тот же сайт много раз или много разных сайтов? Если много разных сайтов, я думаю, что urllib2 хорош. Если делать один и тот же сайт снова и снова, у меня была личная удача с urllib3 http://code.google.com/p/urllib3/

2) BeautifulSoup прост в использовании, но довольно медленный. Если вам нужно его использовать, обязательно разложите теги, чтобы избавиться от утечек памяти.. или это, скорее всего, приведет к проблемам с памятью (это было для меня).

Как выглядит ваша память и процессор? Если вы максимизируете свой процессор, убедитесь, что вы используете реальные тяжелые потоки, поэтому вы можете работать на более чем 1 ядре.

Ответ 8

Как насчет использования pycurl?

Вы можете apt-получить его

$ sudo apt-get python-pycurl

Ответ 9

Во-первых, вы должны попробовать многопоточные/многопроцессорные пакеты. В настоящее время три популярные: multiprocessing; concurrent.futures и [threading] [3]. Эти пакеты могут помочь вам открыть несколько URL-адресов одновременно, что может увеличить скорость.

Что еще более важно, после использования многопоточной обработки, и если вы попытаетесь одновременно открыть сотни URL-адресов, вы найдете urllib.request.urlopen очень медленно, и открытие и чтение контекста станет самой трудоемкой частью. Поэтому, если вы хотите сделать это еще быстрее, вы должны попробовать пакеты запросов, request.get(url).content() быстрее, чем urllib.request.urlopen(url).read().

Итак, здесь я перечисляю два примера для быстрого анализа нескольких URL-адресов, а скорость быстрее, чем другие ответы. Первый пример использует классический пакет потоковой передачи и генерирует одновременно сотни потоков. (Один тривиальный недостаток заключается в том, что он не может сохранить первоначальный порядок тикера.)

import time
import threading
import pandas as pd
import requests
from bs4 import BeautifulSoup


ticker = pd.ExcelFile('short_tickerlist.xlsx')
ticker_df = ticker.parse(str(ticker.sheet_names[0]))
ticker_list = list(ticker_df['Ticker'])

start = time.time()

result = []
def fetch(ticker):
    url = ('http://finance.yahoo.com/quote/' + ticker)
    print('Visit ' + url)
    text = requests.get(url).content
    soup = BeautifulSoup(text,'lxml')
    result.append([ticker,soup])
    print(url +' fetching...... ' + str(time.time()-start))



if __name__ == '__main__':
    process = [None] * len(ticker_list)
    for i in range(len(ticker_list)):
        process[i] = threading.Thread(target=fetch, args=[ticker_list[i]])

    for i in range(len(ticker_list)):    
        print('Start_' + str(i))
        process[i].start()



    # for i in range(len(ticker_list)):
    #     print('Join_' + str(i))    
    #     process[i].join()

    print("Elapsed Time: %ss" % (time.time() - start))

Во втором примере используется пакет многопроцессорности, и это немного более просто. Поскольку вам просто нужно указать количество пулов и отобразить функцию. Порядок не изменится после извлечения контекста, и скорость похожа на первый пример, но намного быстрее, чем на другой метод.

from multiprocessing import Pool
import requests
from bs4 import BeautifulSoup
import pandas as pd
import os
import time

os.chdir('file_path')

start = time.time()

def fetch_url(x):
    print('Getting Data')
    myurl = ("http://finance.yahoo.com/q/cp?s=%s" % x)
    html = requests.get(myurl).content
    soup = BeautifulSoup(html,'lxml')
    out = str(soup)
    listOut = [x, out]
    return listOut

tickDF = pd.read_excel('short_tickerlist.xlsx')
li = tickDF['Ticker'].tolist()    

if __name__ == '__main__':
    p = Pool(5)
    output = p.map(fetch_url, ji, chunksize=30)
    print("Time is %ss" %(time.time()-start))