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