我有一个Fastapi与python做某种网页抓取。api做抓取部分正确,我确信通过测试,但它显示了这个错误,当我访问api页面:
2022-07-08T09:15:12.564152+00:00 app[worker.1]: INFO: Started server process [4]
2022-07-08T09:15:12.564200+00:00 app[worker.1]: INFO: Waiting for application startup.
2022-07-08T09:15:12.564650+00:00 app[worker.1]: INFO: Application startup complete.
2022-07-08T09:15:12.565232+00:00 app[worker.1]: INFO: Uvicorn running on http://0.0.0.0:47436 (Press CTRL+C to quit)
2022-07-08T09:16:05.643153+00:00 heroku[router]: at=error code=H14 desc="No web processes running" method=GET path="/" host=cryptic-plateau-86689.herokuapp.com request_id=504c098c-a538-418b-898c-70ed38496780 fwd="156.146.59.25" dyno= connect= service= status=503 bytes= protocol=https
这是我的脚本的一小段
dict = Scraping().get_books() # this is the web scraping part
app = FastAPI()
@ app.get("/")
def home():
"""Gets everything"""
return dict
这是我的Procfile:
worker: uvicorn main:app --host=0.0.0.0 --port=${PORT:-5000}
请注意,我尝试使用web而不是worker,但我随后得到另一个错误
Error R10 (Boot timeout) -> Web process failed to bind to $PORT within 60 seconds of launch
注意Scraping().get_books()
需要很长时间(2-5分钟),这就是为什么我认为使用web
时会导致超时。
请记住,我是一个初学者,这是我的想法:我认为worker
可以做网页抓取部分,但不能处理api部分。另一方面,web
可以处理api部分,但不能进行网页抓取。这个理论正确吗?如果是,我如何同时使用web和worker来完成不同的任务?
你自己已经部分回答了这个问题:你得到heroku错误的原因是你没有定义一个web
进程,你需要使用它来暴露一个web API。你也给出了你得到其他错误的原因,当你做使用web
进程:第一行(dict = Scraping().get_books()
)需要2-5分钟才能运行,这意味着我们"卡住了";在第一行等待,所以实际的FastAPI应用程序直到这2-5分钟后才启动,而heroku有1分钟的超时来启动API。
另外,旁注:dict
在Python中是一个保留关键字,所以你真的不应该将它用于变量名。试着找一个更具描述性的名称,例如book_dict
.
那么我们可以做些什么来解决这个问题呢?第一,刮擦应该多久运行一次?目前,您只运行一次(在启动应用程序时),然后它就会被修复,直到您重新启动它。这对我来说似乎有点奇怪(在这种情况下,您可以运行一次抓取部分,将其保存到json
文件,然后读取并返回它)。所以,我假设你至少有时想要刷新它。您可以在home
方法中运行Scraping().get_books()
,但通常认为HTTP事务超过几秒是不好的做法,并且通常会导致超时。
首先,我要考虑为什么刮花的时间太长了。您是否要浏览很多页面,如果是的话,您是否可以将其拆分为一个占用一系列页面的函数?或者,看看你是否可以直接从底层API获取数据,而不是使用selenium(参见这个视频)。
但是如果不可能加速抓取,有几种方法可以处理"长时间运行的事务";这个问题没有固定的解决办法,但我还是提出一些建议:
-
最简单的解决方案可能是在相同的进程中运行网页抓取,但是在后台线程中:
import asyncio book_dict = {} async def refresh_books(): global book_dict book_dict = await asyncio.to_thread(Scraping().get_books)
然后你可以使用fastapi-utils库的
@repeat_every
每天运行几次。 -
看一个任务队列库,例如芹菜。这是一种运行后台任务的更健壮的方式(但也更复杂),其中您将同时拥有
web
和worker
进程,并使用消息队列进行通信(我建议在heroku上使用RabbitMQ)。然后你会有一个API方法开始抓取任务并立即返回任务ID,然后另一个方法来获取任务结果使用ID。在这里,如果您愿意,还可以将该作业安排为每天运行几次。
编辑:您提到了公开一个单独的端点来抓取数据并将其保存到JSON文件中。在这种情况下,我会选择使用FastAPI内置的BackgroundTask,在那里你创建一个后台任务来运行抓取,然后保存json文件。类似以下语句:
import json
from fastapi import BackgroundTasks, FastAPI, status
def scrape_books():
book_dict = Scraping().get_books()
with open("books.json", "w") as f:
json.dump(book_dict, f)
@app.post("/update-data", status_code=status.HTTP_202_ACCEPTED)
def send_notification(background_tasks: BackgroundTasks):
background_tasks.add_task(scrape_books)
return {"message": "Scraping books in the background"}