python - 在合理的时间内通过登录抓取许多 URL

python - Scrape many URL's with Login in reasonable time

我正在尝试从我需要登录才能查看实际内容的网站上抓取一些数据。一切正常,但每个请求大约需要 5 秒,这对我的需求来说是一种减慢的方式(>5000 个要抓取的 url)。似乎有更快的方法,如 asyncio aiohttp 模块。 然而,我在网上找到的所有示例都没有显示如何登录站点然后使用这些工具。

所以我基本上需要一个简单易懂的例子来做这样的事情。

我试图重建这个例子: https://realpython.com/python-concurrency/#what-is-concurrency 使用我的代码,但没有用。我还尝试了 requests_html 中的 AsyncHTMLSession(),它返回了一些内容,但似乎不记得登录了。

到目前为止,这是我的代码:

import requests
from bs4 import BeautifulSoup

payload = {
"name" : "username",
"password" : "example_pass",
"destination" : "MAS_Management_UserConsole",
"loginType" : ""
}

links = [several urls]

### stuff with requests
with requests.Session() as c:
    c.get('http://boldsystems.org/')
    c.post('http://boldsystems.org/index.php/Login', data = payload)

def return_id(link):
    page = c.get(link).content
    soup = BeautifulSoup(page, 'html.parser')
    return soup.find(id = 'processidLC').text

for link in links:
    print(return_id(link))

查看 asyncio 并使用 asyncio.gather 函数。

将此 "links = [several urls]" 行下方的所有内容包装在一个方法中。

注意这不是线程安全的,所以不要在方法内更改变量。

此外,这是线程,因此可用于使用 asyncio.sleep(randint(0,2)) 来延迟某些线程,因此它不会同时触发所有线程。

然后使用 asyncio 使用新的 url 调用下面的方法,就像这样

tasks =[]
for url in urls:
    tasks.append(wrapped_method(url))

results = asyncio.gather(*tasks)

希望对您有所帮助。

否则看https://github.com/jreese/aiomultiprocess

您似乎已经在使用 requests,因此您可以尝试 requests-async。下面的示例应该可以帮助您解决问题的“in reasonable time”部分,只需相应地调整 parse_html 函数以搜索您的 HTML 标签。默认情况下,它将 运行 50 个并行请求 (MAX_REQUESTS) 以不耗尽系统资源(文件描述符等)。

示例:

import asyncio
import requests_async as requests
import time

from bs4 import BeautifulSoup
from requests_async.exceptions import HTTPError, RequestException, Timeout


MAX_REQUESTS = 50
URLS = [
    'http://envato.com',
    'http://amazon.co.uk',
    'http://amazon.com',
    'http://facebook.com',
    'http://google.com',
    'http://google.fr',
    'http://google.es',
    'http://google.co.uk',
    'http://internet.org',
    'http://gmail.com',
    'http://whosebug.com',
    'http://github.com',
    'http://heroku.com',
    'http://djangoproject.com',
    'http://rubyonrails.org',
    'http://basecamp.com',
    'http://trello.com',
    'http://yiiframework.com',
    'http://shopify.com',
    'http://airbnb.com',
    'http://instagram.com',
    'http://snapchat.com',
    'http://youtube.com',
    'http://baidu.com',
    'http://yahoo.com',
    'http://live.com',
    'http://linkedin.com',
    'http://yandex.ru',
    'http://netflix.com',
    'http://wordpress.com',
    'http://bing.com',
]


class BaseException(Exception):
    pass


class HTTPRequestFailed(BaseException):
    pass


async def fetch(url, timeout=5):
    async with requests.Session() as session:
        try:
            resp = await session.get(url, timeout=timeout)
            resp.raise_for_status()
        except HTTPError:
            raise HTTPRequestFailed(f'Skipped: {resp.url} ({resp.status_code})')
        except Timeout:
            raise HTTPRequestFailed(f'Timeout: {url}')
        except RequestException as e:
            raise HTTPRequestFailed(e)
    return resp


async def parse_html(html):
    bs = BeautifulSoup(html, 'html.parser')
    if not html: print(html)
    title = bs.title.text.strip()
    return title if title else "Unknown"


async def run(sem, url):
    async with sem:
        start_t = time.time()
        resp = await fetch(url)
        title = await parse_html(resp.text)
        end_t = time.time()
        elapsed_t = end_t - start_t
        r_time = resp.elapsed.total_seconds()
        print(f'{url}, title: "{title}" (total: {elapsed_t:.2f}s, request: {r_time:.2f}s)')
        return resp


async def main():
    sem = asyncio.Semaphore(MAX_REQUESTS)
    tasks = [asyncio.create_task(run(sem, url)) for url in URLS]
    for f in asyncio.as_completed(tasks):
        try:
            result = await f
        except Exception as e:
            print(e)


if __name__ == '__main__':
    asyncio.run(main())

输出:

# time python req.py 
http://google.com, title: "Google" (total: 0.69s, request: 0.58s)
http://yandex.ru, title: "Яндекс" (total: 2.01s, request: 1.65s)
http://github.com, title: "The world’s leading software development platform · GitHub" (total: 2.12s, request: 1.90s)
Timeout: http://yahoo.com
...

real    0m6.868s
user    0m3.723s
sys 0m0.524s

现在,这可能仍然无法帮助您解决日志记录问题。您要查找的 HTML 标签(或整个网页)可能由 JavaScript 生成,因此您需要 requests-html 等使用无头浏览器读取内容的工具由 JavaScript.

渲染

也有可能您的登录表单正在使用 CSRF 保护,例如登录到 Django 管理后台:

>>> import requests
>>> s = requests.Session()
>>> get = s.get('http://localhost/admin/')
>>> csrftoken = get.cookies.get('csrftoken')
>>> payload = {'username': 'admin', 'password': 'abc123', 'csrfmiddlewaretoken': csrftoken, 'next': '/admin/'}
>>> post = s.post('http://localhost/admin/login/?next=/admin/', data=payload)
>>> post.status_code
200

我们首先使用会话执行获取请求,从 csrftoken cookie 中获取令牌,然后我们使用两个隐藏的表单字段登录:

<form action="/admin/login/?next=/admin/" method="post" id="login-form">
  <input type="hidden" name="csrfmiddlewaretoken" value="uqX4NIOkQRFkvQJ63oBr3oihhHwIEoCS9350fVRsQWyCrRub5llEqu1iMxIDWEem">
  <div class="form-row">
    <label class="required" for="id_username">Username:</label>
    <input type="text" name="username" autofocus="" required="" id="id_username">
  </div>
  <div class="form-row">
    <label class="required" for="id_password">Password:</label> <input type="password" name="password" required="" id="id_password">
    <input type="hidden" name="next" value="/admin/">
  </div>
    <div class="submit-row">
    <label>&nbsp;</label>
    <input type="submit" value="Log in">
  </div>
</form>

注:示例使用Python 3.7+