レコード店でマイナーなCDをディグるときのように、GitHubの検索APIでマイナーなファイルを探したい。そんな日もあるでしょう。
ということで、メジャーなやつは除外するためにまずはレポジトリ数が多いRユーザをリストアップします。
library(purrr) library(gh) estimate_pages <- function(threshold = 100L) { res <- gh("/search/users", q = glue::glue("repos:>{threshold} language:R -user:cran"), sort = "repositories", page = 1L, per_page = 1L) message("total_count: ", res$total_count) ceiling(res$total_count / 100L) } do_search_user <- function(page, threshold = 100L) { res <- gh("/search/users", q = glue::glue("repos:>{threshold} language:R"), sort = "repositories", page = page, per_page = 100L) login_names <- map_chr(res$items, "login") types <- map_chr(res$items, "type") Sys.sleep(20) tibble::tibble( login_names, types ) } # 何ページ取得すればいいかをまず見る(この記事を書く時点では8ページ) pages <- estimate_pages(50L) # 一気に取得 users <- map_df(seq_len(pages), do_search_user, threshold = 50L)
結果はこんな感じです。
head(users) #> # A tibble: 6 x 2 #> login_names types #> <chr> <chr> #> 1 cran Organization #> 2 Bioconductor-mirror Organization #> 3 Libardo1 User #> 4 defc0n1 User #> 5 jeperez User #> 6 chaabni User
-user:yutannihilation
のように、-
をつけるとNOT検索をすることができます。
ということで、さっきのユーザをすべて検索クエリに入れてみます。ユーザはuser:
、組織はorg:
です。
users_query <- users %>% mutate(types = recode(types, User = "user", Organization = "org")) %>% { sprintf("-%s:%s", .$types, .$login_names) } %>% paste(collapse = " ") stringr::str_sub(users_query, 1, 100) #> [1] "-org:cran -org:Bioconductor-mirror -user:Libardo1 -user:defc0n1 -user:jeperez -user:chaabni -user:mi" nchar(users_query) #> [1] 12733
これでコードを検索してみます。
res <- gh::gh("/search/code", q = glue::glue("filename:NAMESPACE fork:false export {users_query}"), page = 1, per_page = 100L) #> No encoding supplied: defaulting to UTF-8. #> No encoding supplied: defaulting to UTF-8. #> Error in gh::gh("/search/code", q = glue::glue("filename:NAMESPACE fork:false export {users_query}"), : #> GitHub API error (414): #> <html> #> <head><title>414 Request-URI Too Large</title></head> #> <body bgcolor="white"> #> <center><h1>414 Request-URI Too Large</h1></center> #> <hr><center>nginx</center> #> </body> #> </html> #> #> In addition: Warning message: #> Response came back as html :(
なんということでしょう。長すぎると怒られました。そらそうか。
どうもいろいろ試した結果、制限値は6000~8000くらいにあるみたいです。
ということで、クエリを6000字くらいに切り詰めます。まず空白のインデックスを取ります。
users_query_boundaries <- stringr::str_locate_all(users_query, " ")[[1]][, "start"] head(users_query_boundaries) #> [1] 10 35 50 64 78 92
6000以下で最大の空白の位置を調べます。
users_query_boundaries %>% keep(`<=`, 6000) %>% max #> [1] 5987
str_sub()
でここまで切り詰めます。
users_query_6000 <- substr(users_query, 1, 5987 - 1)
これを検索APIに投げると、まあうまくいったっぽいです。
res <- gh::gh("/search/code", q = glue::glue("filename:NAMESPACE fork:false export {users_query_6000}"), page = 1, per_page = 1L) res$total_count #> [1] 19641
うーん、これでも2万件近くあるのか…(GitHubの検索で取れるのは1000件まで)