首页 > 解决方案 > GAE gunicorn flask 处理需要 30-60 分钟的请求的最佳方式

问题描述

我用烧瓶构建了一个应用程序,它应该能够爬取一些数据。第一步是使用 Youtube Data API 获取有关用户的一些数据,包括用户上传的所有视频的列表。这完全可以正常工作!在获得视频 ID 列表后,我尝试在 youtube 上抓取所有这些视频,以提取所有视频的点赞数和观看次数,并将它们加起来为 2 个大数字。我在没有 gunicorn 的情况下在本地测试它,而不是在应用程序引擎中,它工作正常!但是当用户上传 6700 个视频时,可能需要 30 分钟才能完成请求(本地有效)。 在此处输入图像描述 当我尝试在 GAE 中运行相同的代码时,它会在几分钟后返回 502 Bad Gateway,但在日志中我看到它仍在爬行。这是 GET 502: 502 错误的网关请求

工人继续刮了几分钟。 日志结束

这是我编写的爬取代码:这是我的 app.yaml。使用 -t 36000 Worker 可以静默一小时,直到它们被杀死并重新启动。

runtime: python37
service: crawler
entrypoint: . ./env.inc.sh && gunicorn -t 36000 -b :$PORT app:app

这是我的 app.py 中的路由,称为:

@app.route('/youtube/username/<user>')
def youtubeStatistics(user):
    response = crawler.crawl_youtube_user(os.environ['YOUTUBE_API_KEY'], user)
    if response:
        return jsonify(response), 200
    else:
        return jsonify({"prettyMessage": "Quota Limit maybe Exceeded"}), 403

这些是我使用的爬虫功能:

def scrape_url(url):
    r = requests.get(url)
    page = r.text
    soup = bs(page, 'html.parser')
    return soup

def crawl_youtube_user(KEY, username):
    youtube = set_up(KEY)
    request = youtube.channels().list(
        part="snippet,contentDetails,statistics",
        forUsername=username
    )
    uploadPlaylistId = ""
    data = {}
    try:
        response = request.execute()
    except:
        return {}
    if (response["pageInfo"]["totalResults"] > 0):
        stats = response["items"][0]["statistics"]
        data["subscriberCount"] = stats["subscriberCount"]
        data["videoCount"] = stats["videoCount"]
        data["publishedAt"] = response["items"][0]["snippet"]["publishedAt"]
        uploadPlaylistId = response["items"][0]["contentDetails"]["relatedPlaylists"]["uploads"]
        request = youtube.playlistItems().list(
            part="snippet,contentDetails",
            maxResults=50,
            playlistId=uploadPlaylistId
        )
        videoIds = []
        while True:
            try:
                response = request.execute()
            except:
                return {}
            for vid in response["items"]:
                videoIds.append(vid["snippet"]["resourceId"]["videoId"])
            if "nextPageToken" not in response:
                break
            else:
                request = youtube.playlistItems().list(
                    part="snippet,contentDetails",
                    maxResults=50,
                    playlistId=uploadPlaylistId,
                    pageToken=response["nextPageToken"]
                )
        data.update(crawl_youtube_videos(videoIds))
    return data


def crawl_youtube_videos(ids):
    data = {'viewCount': 0, 'videoLikes': 0}
    counter = 0
    idlength = len(ids)
    for id in ids:
        counter += 1
        print('{}/{}: Scraping Youtube videoId {}'.format(counter,idlength,id))
        soup = scrape_url('https://www.youtube.com/watch?v={}&gl=DE&hl=de'.format(id))
        try:
            data['viewCount'] += int(soup.find('div', class_='watch-view-count').getText().split(' ')[0].replace('.', '').replace(',', ''))
        except:
            print("Error while trying to extract the views of a Video: {}.".format(sys.exc_info()[0]))
        try:
            data['videoLikes'] += int(soup.find("button",{"title": "Mag ich"}).find("span").getText().replace('.', '').replace(',', ''))
        except:
            print("Error while trying to extract the likes of a Video: {}.".format(sys.exc_info()[0]))
    return data

我不想使用更多线程或类似的东西,以使整个过程更快!如果我在短时间内爬到许多网站,我害怕我的 IP 被阻止或类似的事情。我只是尝试让请求保持有效,直到我得到我想要的响应。那么是否有更多机制可以保护 GAE-App 免受长响应时间或类似的影响?处理需要 30-60 分钟的请求的最佳方式是什么?

标签: google-app-engineflaskbeautifulsoupyoutube-data-apigunicorn

解决方案


您应该考虑使用像CeleryRQ这样的后台任务队列。

到位后,您的请求会将作业排队。然后您可以查询任务队列并根据需要获取作业状态。

是开始使用这些选项的一个很好的资源。


推荐阅读