Индекс вне диапазона при отправке запросов в цикле - PullRequest
0 голосов
/ 23 ноября 2018

Я сталкиваюсь с ошибкой индекса вне диапазона, когда пытаюсь получить число участников проекта GitHub в цикле.После некоторых итераций (которые работают отлично) он просто выдает это исключение.Понятия не имею, почему ...

    for x in range(100):
        r = requests.get('https://github.com/tipsy/profile-summary-for-github')  
        xpath = '//span[contains(@class, "num") and following-sibling::text()[normalize-space()="contributors"]]/text()'
        contributors_number = int(html.fromstring(r.text).xpath(xpath)[0].strip().replace(',', ''))
        print(contributors_number) # prints the correct number until the exception

Вот исключение.

----> 4     contributors_number = int(html.fromstring(r.text).xpath(xpath)[0].strip().replace(',', ''))
IndexError: list index out of range

Ответы [ 3 ]

0 голосов
/ 23 ноября 2018

GitHub блокирует ваши повторные запросы.Не просматривайте сайты в быстрой последовательности, многие операторы сайтов активно блокируют слишком много запросов.В результате возвращаемое содержимое больше не соответствует вашему запросу XPath.

Вы должны использовать REST API, который GitHub предоставляет , для получения статистики проекта, например количества участников, и выследует реализовать какое-то ограничение скорости.Нет необходимости извлекать одно и то же число 100 раз, количество участников не меняется так быстро.

Ответы API включают информацию о том, сколько запросов вы можете сделать во временном окне , и вы можете использовать условные запросы только для того, чтобы нести расходы на ограничение скорости, когда данные действительноизменилось:

import requests
import time
from urllib.parse import parse_qsl, urlparse

owner, repo = 'tipsy', 'profile-summary-for-github'
github_username = '....'
# token = '....'   # optional Github basic auth token
stats = 'https://api.github.com/repos/{}/{}/contributors'

with requests.session() as sess:
    # GitHub requests you use your username or appname in the header
    sess.headers['User-Agent'] += ' - {}'.format(github_username)
    # Consider logging in! You'll get more quota
    # sess.auth = (github_username, token)

    # start with the first, move to the last when available, include anonymous
    last_page = stats.format(owner, repo) + '?per_page=100&page=1&anon=true'

    while True:
        r = sess.get(last_page)
        if r.status_code == requests.codes.not_found:
            print("No such repo")
            break
        if r.status_code == requests.codes.no_content:
            print("No contributors, repository is empty")
            break
        if r.status_code == requests.codes.accepted:
            print("Stats not yet ready, retrying")
        elif r.status_code == requests.codes.not_modified:
            print("Stats not changed")
        elif r.ok:
            # success! Check for a last page, get that instead of current
            # to get accurate count
            link_last = r.links.get('last', {}).get('url')
            if link_last and r.url != link_last:
                last_page = link_last
            else:
                # this is the last page, report on count
                params = dict(parse_qsl(urlparse(r.url).query))
                page_num = int(params.get('page', '1'))
                per_page = int(params.get('per_page', '100'))
                contributor_count = len(r.json()) + (per_page * (page_num - 1))
                print("Contributor count:", contributor_count)
            # only get us a fresh response next time
            sess.headers['If-None-Match'] = r.headers['ETag']

        # pace ourselves following the rate limit
        window_remaining = int(r.headers['X-RateLimit-Reset']) - time.time()
        rate_remaining = int(r.headers['X-RateLimit-Remaining'])
        # sleep long enough to honour the rate limit or at least 100 milliseconds
        time.sleep(max(window_remaining / rate_remaining, 0.1))

Выше используется запрос объект сеанса для обработки повторных заголовков и обеспечения возможности повторного использования соединений, где это возможно.

Хорошая библиотека, такаятак как github3.py (случайно написанный основным участником requests) позаботится о большинстве этих деталей для вас.

Если вы действительно хотите продолжать чистить сайт напрямую,Вы рискуете, что операторы сайта полностью блокируют вас.Попытайтесь взять на себя некоторую ответственность, не стучите по сайту постоянно.

Это означает, что по крайней мере , вы должны соблюдать Retry-After заголовок , который GitHub дает вамна 429:

if not r.ok:
    print("Received a response other that 200 OK:", r.status_code, r.reason)
    retry_after = r.headers.get('Retry-After')
    if retry_after is not None:
        print("Response included a Retry-After:", retry_after)
        time.sleep(int(retry_after))
else:
    # parse OK response
0 голосов
/ 23 ноября 2018

Теперь это прекрасно работает для меня при использовании API.Наверное, самый чистый способ сделать это.

import requests
import json

url = 'https://api.github.com/repos/valentinxxx/nginxconfig.io/commits?&per_page=100'
response = requests.get(url)
commits = json.loads(response.text)

commits_total = len(commits)
page_number = 1
while(len(commits) == 100):
    page_number += 1
    url = 'https://api.github.com/repos/valentinxxx/nginxconfig.io/commits?&per_page=100'+'&page='+str(page_number)
    response = requests.get(url)
    commits = json.loads(response.text)
    commits_total += len(commits)
0 голосов
/ 23 ноября 2018

Вероятно, вы получаете 429. - Слишком много запросов, поскольку вы запускаете запросы один за другим.

Возможно, вы захотите изменить свой код следующим образом:

import time

for index in range(100):
    r = requests.get('https://github.com/tipsy/profile-summary-for-github')  
    xpath = '//span[contains(@class, "num") and following-sibling::text()[normalize-space()="contributors"]]/text()'
    contributors_number = int(html.fromstring(r.text).xpath(xpath)[0].strip().replace(',', ''))
    print(contributors_number)
    time.sleep(3) # Wait a bit before firing of another request

Еще лучше было бы:

import time

for index in range(100):
    r = requests.get('https://github.com/tipsy/profile-summary-for-github')
    if r.status_code in [200]:  # Check if the request was successful  
        xpath = '//span[contains(@class, "num") and following-sibling::text()[normalize-space()="contributors"]]/text()'
        contributors_number = int(html.fromstring(r.text).xpath(xpath)[0].strip().replace(',', ''))
        print(contributors_number)
    else:
        print("Failed fetching page, status code: " + str(r.status_code))
    time.sleep(3) # Wait a bit before firing of another request
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...