2014-11-08 2 views
1

У меня есть сценарий Python, который я хотел бы запускать каждый день, и я бы предпочел, чтобы его работала всего 1-2 часа. В настоящее время он настроен на использование 4 различных API-интерфейсов для заданного URL-адреса, получения результатов и сохранения данных в базе данных PostgreSQL. Проблема в том, что у меня более 160 000 URL-адресов, и сценарий заканчивается очень долго - я провел некоторые предварительные тесты, и для каждого URL в его текущем формате потребуется более 36 часов. Итак, мой вопрос сводится к: следует ли оптимизировать мой скрипт для одновременного запуска нескольких потоков? Или я должен масштабировать количество серверов, которые я использую? Очевидно, что второй подход будет более дорогостоящим, поэтому я бы предпочел, чтобы в одном экземпляре выполнялось несколько потоков.Улучшить скорость скрипта Python: многопоточность или несколько экземпляров?

Я использую созданную мной библиотеку (SocialAnalytics), которая предоставляет методы для ударов по различным конечным точкам API и анализа результатов. Вот как я мой сценарий конфигурации:

import psycopg2 
from socialanalytics import pinterest 
from socialanalytics import facebook 
from socialanalytics import twitter 
from socialanalytics import google_plus 
from time import strftime, sleep 

conn = psycopg2.connect("dbname='***' user='***' host='***' password='***'") 
cur = conn.cursor() 

# Select all URLs 
cur.execute("SELECT * FROM urls;") 
urls = cur.fetchall() 

for url in urls: 

    # Pinterest 
    try: 
     p = pinterest.getPins(url[2]) 
    except: 
     p = { 'pin_count': 0 } 
    # Facebook 
    try: 
     f = facebook.getObject(url[2]) 
    except: 
     f = { 'comment_count': 0, 'like_count': 0, 'share_count': 0 } 
    # Twitter 
    try: 
     t = twitter.getShares(url[2]) 
    except: 
     t = { 'share_count': 0 } 
    # Google 
    try: 
     g = google_plus.getPlusOnes(url[2]) 
    except: 
     g = { 'plus_count': 0 } 

    # Save results 
    try: 
     now = strftime("%Y-%m-%d %H:%M:%S") 
     cur.execute("INSERT INTO social_stats (fetched_at, pinterest_pins, facebook_likes, facebook_shares, facebook_comments, twitter_shares, google_plus_ones) VALUES(%s, %s, %s, %s, %s, %s, %s, %s);", (now, p['pin_count'], f['like_count'], f['share_count'], f['comment_count'], t['share_count'], g['plus_count'])) 
     conn.commit() 
    except: 
     conn.rollback() 

Вы можете видеть, что каждый вызов API использует Requests library, который является синхронным, блокируя дело. После некоторых предварительных исследований я обнаружил Treq, который является API поверх Twisted. Асинхронный, неблокирующий характер Twisted кажется хорошим кандидатом для улучшения моего подхода, но я никогда не работал с ним, и я не уверен, как именно (и если) это поможет мне достичь моей цели.

Любое руководство очень ценится!

+0

Требуется ли для разных запросов URL-адрес разделяемое пространство памяти? В противном случае простым решением будет использование многопроцессорности. Для некоторого быстрого кода см. Http://stackoverflow.com/questions/3842237/parallel-processing-in-python – duhaime

+0

Нет, им не требуется разделяемое пространство памяти. Единственный способ, я могу думать о том, чтобы избавиться от необходимого количества времени, - это запустить 10 процессов, каждый из которых будет обрабатывать собственный URL. Однако я не совсем уверен, как это сделать. С этим подходом я направляюсь в правильном направлении? https://gist.github.com/anonymous/b337afbd8f92d3991b47 – Abundnce10

+0

Извините, просто получил это. Кажется, все сработало :) – duhaime

ответ

2

Сначала вы должны измерить время, которое ваш сценарий проводит на каждом шагу. Может быть, вы обнаружите что-то интересное :)

Во-вторых, вы можете разделить ваши URLs на куски:

chunk_size = len(urls)/cpu_core_count; // don't forget about remainder of division

После выполнения этих шагов можно использовать multiprocessing для обработки каждого куска параллельно. Вот вам пример:

import multiprocessing as mp 

p = mp.Pool(5) 

# first solution 
for urls_chunk in urls: # urls = [(url1...url6),(url7...url12)...] 
    res = p.map(get_social_stat, urls_chunk) 
    for record in res: 
     save_to_db(record) 

# or, simple 
res = p.map(get_social_stat, urls) 

for record in res: 
    save_to_db(record) 

Также gevent может вам помочь. Потому что он может оптимизировать затраты времени на обработку последовательности синхронных запросов блокировки.

+0

Конечно, вы можете создать 10 процессов. Но лучше найти оптимальное число по результатам теста производительности. Попробуйте разобрать 100 URL-адресов, используя различное количество процессов. Попробуйте загрузить ваш процессор на 70-80 процентов. – Jimilian

+0

Я использовал ваш код выше и нашел, что 10 процессов привели к тому, что мой процессор стал около 35%. Однако после нескольких тестов, которые я обнаружил, я попал в предел для API Facebook. Я теперь получаю '(# 4) предел запроса приложения, достигнутый' http://stackoverflow.com/questions/14092989/facebook-api-4-application-request-limit-reached. Спасибо за вашу помощь! – Abundnce10

Смежные вопросы