通过并行处理丢失HTTPS请求

时间:2018-03-17 19:44:47

标签: python parallel-processing

我使用以下两个类方法从Questrade API(http://www.questrade.com/api/documentation/rest-operations/market-calls/markets-quotes-id)请求信息。我有超过11,000个股票代码,我请求Questrade API批量为100个符号。

import  requests
from joblib import Parallel, delayed

def parallel_request(self, elem, result, url, key):
    response = requests.get(''.join((url, elem)), headers=self.headers)
    result.extend(response.json().get(key))

Parallel(n_jobs=-1, backend="threading")(
         delayed(self.parallel_request)(elem, self.symbol_ids_list, self.uri, 'symbols')\
         for elem in self.batch_result
     )

如果我使用Parallel类生成超过110个HTTPS请求,那么我得到10,500或10,600而不是获得11,000个输出。所以我通过并行处理丢失了数据。请注意,我在这里使用了两个python模块,即joblib(https://github.com/joblib/joblib/issues/651)和请求(https://github.com/requests/requests)。

以下for循环完美运行,所以我知道我的问题在于Parallel类。

for elem in self.batch_result:
       response = requests.get(''.join((self.uri, elem)), headers=self.headers)
       self.symbol_ids_list.extend(response.json().get('symbols'))

如何在不丢失数据的情况下提高上一个for循环的性能?

更新

self.batch_result(简化结果)的示例可以是['AAME,ABAC,ABIL,ABIO,ACERW,ACHN,ACHV,ACRX,ACST,ACTG,ADMA,ADMP,ADOM,ADXS,ADXSW,AEHR,AEMD,AETI,AEY,AEZS,AFMD,AGFSW,AGRX,AGTC,AHPAW,AHPI,AIPT,AKER,AKTX,ALIM,ALJJ,ALQA,ALSK,ALT,AMCN,AMDA,AMMA,AMRH,AMRHW,AMRN,AMRWW,AMTX,ANDAR,ANDAW,ANTH,ANY,APDN,APDNW,APOPW,APPS,APRI,APTO,APVO,APWC,AQB,AQMS,ARCI,ARCW,ARDM,AREX,ARGS,ARLZ,ARQL,ARTW,ARTX,ASFI,ASNA,ASRV,ASTC,ATACR,ATEC,ATHX,ATLC,ATOS,ATRS,AUTO,AVEO,AVGR,AVID,AVXL,AWRE,AXAS,AXON,AXSM,AYTU,AZRX,BASI,BBOX,BBRG,BCACR,BCACW,BCLI,BDSI,BHACR,BHACW,BIOC,BIOL,BIOS,BKEP,BKYI', 'BLDP,BLIN,BLNK,BLNKW,BLPH,BLRX,BMRA,BNSO,BNTC,BNTCW,BOSC,BOXL,BPTH,BRACR,BRACW,BRPAR,BRPAW,BSPM,BSQR,BUR,BURG,BVSN,BVXVW,BWEN,BYFC,CAAS,CADC,CALI,CAPR,CARV,CASI,CASM,CATB,CATS,CBAK,CBLI,CCCL,CCCR,CCIH,CDMO,CDTI,CELGZ,CERCW,CETV,CETX,CETXW,CFBK,CFMS,CFRX,CGEN,CGIX,CGNT,CHCI,CHEK,CHEKW,CHFS,CHKE,CHMA,CHNR,CIDM,CJJD,CKPT,CLDC,CLDX,CLIR,CLIRW,CLNE,CLRB,CLRBW,CLRBZ,CLSN,CLWT,CMSSR,CMSSW,CNACR,CNACW,CNET,CNIT,CNTF,CODA,CODX,COGT,CPAH,CPLP,CPRX,CPSH,CPSS,CPST,CREG,CRIS,CRME,CRNT,CSBR,CTHR,CTIB,CTIC,CTRV,CTXR,CTXRW,CUI', 'CUR,CVONW,CXDC,CXRX,CYCC,CYHHZ,CYRN,CYTR,CYTX,CYTXW,DARE,DCAR,DCIX,DELT,DEST,DFBG,DFFN,DGLY,DHXM,DLPN,DLPNW,DMPI,DOGZ,DOTAR,DOTAW,DRAD,DRIO,DRIOW,DRRX,DRYS,DSKEW,DSWL,DTEA,DTRM,DXLG,DXYN,DYNT,DYSL,EACQW,EAGLW,EARS,EASTW,EBIO,EDAP,EFOI,EGLT,EKSO,ELECW,ELGX,ELON,ELSE,ELTK,EMITF,EMMS,ENG,ENPH,ENT,EPIX,ESEA,ESES,ESTRW,EVEP,EVGN,EVK,EVLV,EVOK,EXFO,EXXI,EYEG,EYEGW,EYES,EYESW,FCEL,FCRE,FCSC,FFHL,FLGT,FLL,FMCIR,FMCIW,FNJN,FNTEW,FORD,FORK,FPAY,FRAN,FRED,FRSX,FSACW,FSNN,FTD,FTEK,FTFT,FUV,FVE,FWP,GALT,GASS,GCVRZ,GEC']

self.uri只是'https://api01.iq.questrade.com/v1/symbols?names=',如上面的Questrade API链接所示。

更新2

马拉特的答案是一个很好的尝试,但没有给我一个更好的结果。第一次测试给了我31,356(如果我将结果除以3,则为10,452)而不是10,900。第二次测试只给了我0或完全进程。

我发现Maximum allowed requests per second是20.链接:http://www.questrade.com/api/documentation/rate-limiting。如何在不考虑新信息的情况下丢失数据的情况下,如何提高上一个for循环的性能?

2 个答案:

答案 0 :(得分:0)

最有可能的原因是,一些HTTP调用因网络负载而失败。要进行测试,请更改parallel_request

def parallel_request(self, elem, result, url, key):
    for i in range(3):  # 3 retries
        try:
            response = requests.get(''.join((url, elem)), headers=self.headers)
        except IOError: 
            continue
        result.extend(response.json().get(key))
        return

更不可能:list.extend不是线程安全的。如果上面的代码段无效,请尝试使用锁定保护extend

import threading
...

lock = threading.Lock()

def parallel_request(self, elem, result, url, key):
    response = requests.get(''.join((url, elem)), headers=self.headers)
    lock.acquire()
    result.extend(response.json().get(key))
    lock.release()

答案 1 :(得分:0)

如果您不使用joblib,可以尝试一些标准库并行处理模块。在python2 / 3中multiprocessing.Pool可用,并提供跨并行线程映射任务的函数。简化版本如下所示:

from multiprocessing import Pool
import requests

HEADERS = {} # define headers here

def parallel_request(symbols):
    response = requests.get('https://api01.iq.questrade.com/v1/symbols?names={}'.format(symbols), headers=HEADERS)
    return response.json()

if __name__ == '__main__':
    p = Pool()
    batch_result = ['AAME,ABAC,ABIL,...',
                    'BLDP,BLIN,BLNK,...',
                    'CUR,CVONW,CXDC,...', 
                     ...]

    p.map(parallel_request, batch_result) # will return a list of len(batch_result) responses

map的异步和可迭代版本,您可能需要更大的作业,当然,您可以为parallel_requests任务添加参数,以避免像我那样硬编码。使用Pool的一个警告是,传递给它的任何参数都必须是可选择的。

在python3中,concurrent.futures模块实际上在文档中有一个很好的多线程url检索示例。只需稍加努力,您就可以使用load_url函数替换该示例中的parallel_request。有一个concurrent.futures版本向后移植到python2作为futures模块。

这些可能需要在重构方面多做一些工作,所以如果有一个坚持joblib的解决方案,请随意选择。如果您的问题很可能是joblib中的错误,那么有很多方法可以使用标准库以多线程方式执行此操作(尽管添加了一些样板文件)。