Разделение как по дате создания, так и по диапазону звезд («сумасшедшее» решение, упомянутое в вопросе) работает довольно хорошо на практике.
Вы можете использовать запрос GraphQL, как этот, чтобы получить только количество репо с 15-20 звезд, которые были созданы в данном диапазоне дат:
query {
search(query: "is:public stars:15..20 created:2016-01-01..2016-01-09", type: REPOSITORY, first: 1) {
repositoryCount
}
}
ответ:
{ "data": { "search": { "repositoryCount": 534 } } }
Для данного диапазона звезд (скажем, 15–20) вы начинаете с длинной диапазон дат (скажем, 2007–2020) и получите счетчик результатов. Если оно превышает 1000, вы делите диапазон дат на две части и получаете результат для каждого. Продолжайте разделять рекурсивно до тех пор, пока каждый диапазон звезд / интервал дат не достигнет 1000 результатов.
Вот код для этого:
def split_interval(a, b):
d = int((b - a) / 2)
return [(a, a + d), (a + d + 1, b)]
def split_by_days(stars, day_start, day_end):
start_fmt = day_start.strftime('%Y-%m-%d')
end_fmt = day_end.strftime('%Y-%m-%d')
q = f'stars:{stars} created:{start_fmt}..{end_fmt}')
c = get_count(q)
if c <= 1000:
query_repos(q, out_file)
else:
days = (day_end - day_start).days
if days == 0:
raise ValueError(f'Can\'t split any more: {stars} / {day_start} .. {day_end}')
for a, b in split_interval(0, days):
dt_a = day_start + timedelta(days=a)
dt_b = day_start + timedelta(days=b)
split_by_days(stars, dt_a, dt_b)
ranges = [
(15, 20), (21, 25), (26, 30),
# ...
(1001, 1500), (1501, 5000), (5001, 1_000_000)
]
for a, b in ranges:
stars = f'{a}..{b}'
split_by_days(stars, datetime(2007, 1, 1), datetime(2020, 2, 2))
Лучше очищать от нижнего диапазона звезд, так как репо более скорее всего, получит звезды, чем потеряет их в процессе очистки.
Для меня это заняло 1102 различных поиска. Вот CSV-файл (~ 50 МБ), собранный с использованием этого подхода со всеми репо, которые имели 15+ звезд на 2020-02-03. См. этот пост и сопровождающий исходный код для получения более подробной информации.