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

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

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

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

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

Ответы [ 9 ]

16 голосов
/ 13 августа 2010

Я переписываю код 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() - 2 с. Лучшее время для fetch_parallel() - 0,9 с.

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

9 голосов
/ 13 августа 2010

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

Скорость чтения веб-страниц, вероятно, ограничена вашим интернет-соединением, а не 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 голосов
/ 09 марта 2013

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

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]
2 голосов
/ 17 июня 2014

Не знаю, почему никто не упоминает 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 (интерпретатор). Хотя он не подлежит глобальной блокировке интерпретатора, это означает, что вы ограничены в том, что вы можете передать новому процессу.

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

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

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

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

2 голосов
/ 13 августа 2010

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

2 голосов
/ 13 августа 2010

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

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

1 голос
/ 13 августа 2010

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

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

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

0 голосов
/ 04 ноября 2016

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

Что еще более важно, после использования многопоточной обработки, и если вы попытаетесь открыть сотни URL одновременно, вы найдетеurllib.request.urlopen работает очень медленно, и открытие и чтение контекста становятся наиболее трудоемкой частью.Так что, если вы хотите сделать это еще быстрее, вам следует попробовать пакеты запросов, запросы.

Итак, здесь я привожу два примера быстрого разбора нескольких 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))
0 голосов
/ 13 августа 2010

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

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

$ sudo apt-get python-pycurl
...