我的应用程序从用户接收一个或多个URL(通常为3-4个URL),从这些URL中抓取某些数据并将这些数据写入数据库。但是,因为抓取这些数据需要一段时间,我正在考虑在一个单独的线程中运行每个抓取,以便抓取+写入数据库可以继续在后台进行,这样用户就不必继续等待。
为了实现这一点,我有(仅限相关部分):
@view_config(route_name="add_movie", renderer="templates/add_movie.jinja2")
def add_movie(request):
post_data = request.POST
if "movies" in post_data:
movies = post_data["movies"].split(os.linesep)
for movie_id in movies:
movie_thread = Thread(target=store_movie_details, args=(movie_id,))
movie_thread.start()
return {}
def store_movie_details(movie_id):
movie_details = scrape_data(movie_id)
new_movie = Movie(**movie_details) # Movie is my model.
print new_movie # Works fine.
print DBSession.add(movies(**movie_details)) # Returns None.
虽然行new_movie
确实打印了正确的报废数据,但DBSession.add()
不起作用。实际上,它只返回None
。
如果我删除线程并只调用方法store_movie_details()
,它就可以正常工作。
发生了什么事?
答案 0 :(得分:2)
首先,Session.add()上的SA文档没有提及该方法的返回值,因此我认为它应该返回None
。
其次,我认为您打算在会话中添加new_movie
,而不是movies(**movie_details)
,无论是什么:)
第三,标准Pyramid会话(使用ZopeTransactionExtension配置的会话)与Pyramid的请求 - 响应周期相关联,这可能会在您的情况下产生意外行为。您需要配置一个单独的会话,您需要在store_movie_details
中手动提交。此会话需要使用scoped_session,因此会话对象是线程本地的,不会跨线程共享。
from sqlalchemy.orm import scoped_session
from sqlalchemy.orm import sessionmaker
session_factory = sessionmaker(bind=some_engine)
AsyncSession = scoped_session(session_factory)
def store_movie_details(movie_id):
session = AsyncSession()
movie_details = scrape_data(movie_id)
new_movie = Movie(**movie_details) # Movie is my model.
session.add(new_movie)
session.commit()
当然,这种方法仅适用于非常轻量级的任务,如果您不介意偶尔丢失任务(例如,当Web服务器重新启动时)。对于任何更严肃的事情都要看看芹菜等,正如Antoine Leclair所暗示的那样。
答案 1 :(得分:0)