我正在为学校做项目,我正在尝试获取有关电影的数据。我已经设法编写了一个脚本来从IMDbPY和Open Movie DB API(omdbapi.com)获取我需要的数据。我遇到的挑战是,我正在尝试获取22,305部电影的数据,每个请求大约需要0.7秒。基本上我当前的脚本大约需要8个小时才能完成。寻找可能同时使用多个请求的任何方式或任何其他建议,以显着加快获取此数据的过程。
import urllib2
import json
import pandas as pd
import time
import imdb
start_time = time.time() #record time at beginning of script
#used to make imdb.com think we are getting this data from a browser
user_agent = 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)'
headers = { 'User-Agent' : user_agent }
#Open Movie Database Query url for IMDb IDs
url = 'http://www.omdbapi.com/?tomatoes=true&i='
#read the ids from the imdb_id csv file
imdb_ids = pd.read_csv('ids.csv')
cols = [u'Plot', u'Rated', u'tomatoImage', u'Title', u'DVD', u'tomatoMeter',
u'Writer', u'tomatoUserRating', u'Production', u'Actors', u'tomatoFresh',
u'Type', u'imdbVotes', u'Website', u'tomatoConsensus', u'Poster', u'tomatoRotten',
u'Director', u'Released', u'tomatoUserReviews', u'Awards', u'Genre', u'tomatoUserMeter',
u'imdbRating', u'Language', u'Country', u'imdbpy_budget', u'BoxOffice', u'Runtime',
u'tomatoReviews', u'imdbID', u'Metascore', u'Response', u'tomatoRating', u'Year',
u'imdbpy_gross']
#create movies dataframe
movies = pd.DataFrame(columns=cols)
i=0
for i in range(len(imdb_ids)-1):
start = time.time()
req = urllib2.Request(url + str(imdb_ids.ix[i,0]), None, headers) #request page
response = urllib2.urlopen(req) #actually call the html request
the_page = response.read() #read the json from the omdbapi query
movie_json = json.loads(the_page) #convert the json to a dict
#get the gross revenue and budget from IMDbPy
data = imdb.IMDb()
movie_id = imdb_ids.ix[i,['imdb_id']]
movie_id = movie_id.to_string()
movie_id = int(movie_id[-7:])
data = data.get_movie_business(movie_id)
data = data['data']
data = data['business']
#get the budget $ amount out of the budget IMDbPy string
try:
budget = data['budget']
budget = budget[0]
budget = budget.replace('$', '')
budget = budget.replace(',', '')
budget = budget.split(' ')
budget = str(budget[0])
except:
None
#get the gross $ amount out of the gross IMDbPy string
try:
budget = data['budget']
budget = budget[0]
budget = budget.replace('$', '')
budget = budget.replace(',', '')
budget = budget.split(' ')
budget = str(budget[0])
#get the gross $ amount out of the gross IMDbPy string
gross = data['gross']
gross = gross[0]
gross = gross.replace('$', '')
gross = gross.replace(',', '')
gross = gross.split(' ')
gross = str(gross[0])
except:
None
#add gross to the movies dict
try:
movie_json[u'imdbpy_gross'] = gross
except:
movie_json[u'imdbpy_gross'] = 0
#add gross to the movies dict
try:
movie_json[u'imdbpy_budget'] = budget
except:
movie_json[u'imdbpy_budget'] = 0
#create new dataframe that can be merged to movies DF
tempDF = pd.DataFrame.from_dict(movie_json, orient='index')
tempDF = tempDF.T
#add the new movie to the movies dataframe
movies = movies.append(tempDF, ignore_index=True)
end = time.time()
time_took = round(end-start, 2)
percentage = round(((i+1) / float(len(imdb_ids))) * 100,1)
print i+1,"of",len(imdb_ids),"(" + str(percentage)+'%)','completed',time_took,'sec'
#increment counter
i+=1
#save the dataframe to a csv file
movies.to_csv('movie_data.csv', index=False)
end_time = time.time()
print round((end_time-start_time)/60,1), "min"
答案 0 :(得分:7)
根据评论中的建议,您应同时获取Feed。可以使用treading
,multiprocessing
或eventlet
来完成此操作。
$ pip install eventlet
eventlet
请参阅:http://eventlet.net/doc/examples.html#web-crawler
eventlet
使用threading
系统负责在线程之间切换。如果您必须访问一些常见的数据结构,这会带来很大的问题,因为您不知道,其他线程当前正在访问您的数据。然后,您开始使用同步块,锁和信号量 - 只是为了同步对共享数据结构的访问。
使用eventlet
它变得更加简单 - 您始终只运行一个线程,并且仅在I / O指令或其他eventlet
调用时在它们之间跳转。其余代码不间断运行且没有风险,另一个线程会搞乱我们的数据。
您只需照顾以下事项:
所有I / O操作必须是非阻塞的(这很容易,eventlet
为您需要的大多数I / O提供非阻塞版本。
您的剩余代码不能是CPU昂贵的,因为它会阻止" green"线程更长的时间和'#34;绿色"多线程将会消失。
eventlet
的一大优势是,它允许以直接的方式编写代码,而不会因为锁定,信号量等而损坏它(
eventlet
应用于您的代码如果我理解正确,提前知道要提取的网址列表,并且在分析中处理的顺序并不重要。这将允许从eventlet
几乎直接复制示例。我看到,索引i
具有一定的意义,因此您可以考虑将url和索引混合为元组并将其作为独立作业处理。
肯定还有其他方法,但我个人发现eventlet
非常容易使用它与其他技术比较,同时获得非常好的结果(特别是在获取提要时)。您只需要掌握主要概念,并且要小心遵循eventlet
要求(保持非阻塞)。
使用requests
进行异步处理的各种软件包,其中一个使用eventlet
并命名为erequests
,请参阅https://github.com/saghul/erequests
import erequests
# have list of urls to fetch
urls = [
'http://www.heroku.com',
'http://python-tablib.org',
'http://httpbin.org',
'http://python-requests.org',
'http://kennethreitz.com'
]
# erequests.async.get(url) creates asynchronous request
async_reqs = [erequests.async.get(url) for url in urls]
# each async request is ready to go, but not yet performed
# erequests.map will call each async request to the action
# what returns processed request `req`
for req in erequests.map(async_reqs):
if req.ok:
content = req.content
# process it here
print "processing data from:", req.url
我们可以获取并以某种方式处理我们需要的所有网址。但是在这个问题中,处理与源数据中的特定记录绑定,因此我们需要将处理后的请求与我们需要的记录索引进行匹配,以获得最终处理的更多细节。
正如我们稍后将看到的,异步处理不遵循请求的顺序,一些处理更快,一些稍后处理,map
产生任何已完成的。
一个选项是将给定URL的索引附加到请求,并在以后处理返回的数据时使用它。
注意:以下示例相当复杂,如果您可以使用上面提供的解决方案,请跳过此步骤。但请确保您没有遇到下面检测到并解决的问题(正在修改网址,重定向后请求)。
import erequests
from itertools import count, izip
from functools import partial
urls = [
'http://www.heroku.com',
'http://python-tablib.org',
'http://httpbin.org',
'http://python-requests.org',
'http://kennethreitz.com'
]
def print_url_index(index, req, *args, **kwargs):
content_length = req.headers.get("content-length", None)
todo = "PROCESS" if req.status_code == 200 else "WAIT, NOT YET READY"
print "{todo}: index: {index}: status: {req.status_code}: length: {content_length}, {req.url}".format(**locals())
async_reqs = (erequests.async.get(url, hooks={"response": partial(print_url_index, i)}) for i, url in izip(count(), urls))
for req in erequests.map(async_reqs):
pass
requests
(以及erequests
)允许定义名为response
的事件的挂钩。每次请求得到一个响应,这个钩子函数被调用,可以做某事甚至修改响应。
以下行定义了一些响应挂钩:
erequests.async.get(url, hooks={"response": partial(print_url_index, i)})
任何钩子的签名都应为func(req, *args, *kwargs)
但是我们需要将钩子函数传递给我们正在处理的url的索引。
为此,我们使用functools.partial
,它允许通过将某些参数固定为特定值来创建简化函数。这正是我们所需要的,如果您看到print_url_index
签名,我们只需要修复index
的值,其余的将符合钩子函数的要求。
在我们的通话中,我们使用简化函数partial
的名称print_url_index
,并为每个网址提供唯一索引。
索引可以在循环中由enumerate
提供,如果参数数量较多,我们可以使用更高效的内存方式并使用count
,这会生成每次增加的数字,默认情况下从0开始
$ python ereq.py
WAIT, NOT YET READY: index: 3: status: 301: length: 66, http://python-requests.org/
WAIT, NOT YET READY: index: 4: status: 301: length: 58, http://kennethreitz.com/
WAIT, NOT YET READY: index: 0: status: 301: length: None, http://www.heroku.com/
PROCESS: index: 2: status: 200: length: 7700, http://httpbin.org/
WAIT, NOT YET READY: index: 1: status: 301: length: 64, http://python-tablib.org/
WAIT, NOT YET READY: index: 4: status: 301: length: None, http://kennethreitz.org
WAIT, NOT YET READY: index: 3: status: 302: length: 0, http://docs.python-requests.org
WAIT, NOT YET READY: index: 1: status: 302: length: 0, http://docs.python-tablib.org
PROCESS: index: 3: status: 200: length: None, http://docs.python-requests.org/en/latest/
PROCESS: index: 1: status: 200: length: None, http://docs.python-tablib.org/en/latest/
PROCESS: index: 0: status: 200: length: 12064, https://www.heroku.com/
PROCESS: index: 4: status: 200: length: 10478, http://www.kennethreitz.org/
这表明:
urls
没有网址,即使是索引2我们还会额外添加/
。这就是为什么在原始网址列表中简单查找响应网址对我们没有帮助的原因。