Mình sẽ hướng dẫn bạn triển khai pipeline nhiều pha để tìm, lọc và gán nhãn các repository GitHub theo truy vấn phức hợp, rồi xuất kết quả máy đọc được (CSV/JSON) kèm cờ chấp nhận/loại và lý do. Ví dụ minh họa sẽ dùng truy vấn in:readme với nhóm từ khóa và hai truy vấn code-level filename:requirements.txt và filename:Dockerfile.
Phân tách yêu cầu và mục tiêu đầu bài
Mục tiêu của phần này là làm rõ phạm vi và đầu ra mong đợi cho một quy trình tìm, lọc và đánh giá các repo GitHub theo truy vấn phức hợp, nhằm xuất danh sách cuối cùng ở dạng máy đọc được (CSV hoặc JSON) kèm cờ chấp nhận/loại và lý do. Mình tóm tắt các ràng buộc thực thi, format đầu ra, phân biệt tiêu chí bắt buộc và tiêu chí chấm điểm, kênh ưu tiên để kiểm tra và tự động hóa, và các rủi ro chính cần giảm thiểu.
- Phạm vi đầu vào và đầu ra
- Đầu vào: một hoặc nhiều truy vấn repository-search (readme/metadata) và một hoặc nhiều truy vấn code-search (filename hoặc content).
- Đầu ra bắt buộc: file CSV hoặc JSON chứa các repo được xét kèm metadata và cờ quyết định. CSV là kênh chính để trao tay đồng đội; JSON dùng cho tích hợp tự động.
- Trường bắt buộc trong CSV/JSON: fullname, htmlurl, stars, size, pushedat, languageprimary, isfork, isarchived, hasrequirements (bool), hasdockerfile (bool), passoverall (bool), score (numeric), failreasons (semicolons separated).
- Tiêu chí bắt buộc vs tiêu chí chấm điểm
- Tiêu chí bắt buộc (must): archived == false (mặc định, có tuỳ chọn khác), repo tồn tại trên public, repo thỏa repository-search qualifiers cơ bản, và file-presence checks theo yêu cầu AND/OR (ví dụ phải có cả requirements.txt và Dockerfile nếu yêu cầu). Nếu một must fail thì repo bị mark reject với lý do tương ứng.
- Tiêu chí chấm điểm (score): stars, recency (pushed_at), size (heuristic), language match percentage, README length, số contributors, CI presence. Tính điểm theo rubric, đặt ngưỡng pass; nếu không đạt nhưng close thì chuyển sang manual review.
- Kênh ưu tiên thực hiện
- Web kiểm tra nhanh: dùng để prototype truy vấn, tinh chỉnh từ khóa, xem counts và sample kết quả trước khi scale. Dùng khi muốn feedback tức thì.
- REST API tự động hóa: kênh chính cho harvest production. Dùng search/repositories và search/code, pagination, total_count check, phân mảnh query khi cần, và calls tới contents, languages, releases, commits để hậu kiểm.
- gh CLI: tuỳ chọn cho thao tác ad-hoc, debug và small-batch automation; không dùng để scale lớn thay cho REST API.
- Rủi ro chính cần giảm thiểu (và biện pháp ngắn)
- Bỏ sót do giới hạn trả về search (totalcount cap): phát hiện totalcount, phân mảnh truy vấn theo stars/pushed/owner buckets.
- Index lag giữa repo metadata và code index: nếu pushed_at mới nhưng code-search không trả file, queue re-check sau vài phút hoặc dùng contents API để xác thực.
- Sai kết hợp query code-level vs repo-level: tách repository-search và code-search, chạy code-search per-filename rồi intersect theo full_name.
- Phân trang, rate limit và retry: luôn pagination đầy đủ, đọc Link headers, dùng backoff và checkpointing.
- Forks/mirrors/duplicates: hậu xử lý loại fork mặc định bằng repo.fork, nhưng cung cấp option giữ fork nếu có commits ahead/releases; dedupe theo full_name normalized.
Kết quả đầu ra phải cho phép audit: lưu toàn bộ truy vấn, buckets dùng để phân mảnh, total_count gốc, và timestamp khi thu thập để truy vết nguyên nhân nếu cần.
Tổng quan về hai loại search của GitHub: repository search vs code search
Repository search và code search là hai công cụ khác nhau về scope, cú pháp qualifier và cách lập chỉ mục. Hiểu rõ khác biệt này là tiền đề để bạn thiết kế pipeline nhiều pha an toàn.
- Scope
- Repository search: tìm trên metadata của repository và các trường như name, description, README (khi dùng in:readme), topics. Nó thuận tiện để lọc theo stars, language, pushed, size, archived, fork. Kết quả trả về là các repo.
- Code search: tìm trên nội dung file đã được lập chỉ mục và từng file là đơn vị tài liệu. Nó phù hợp với filename:, path:, content:, và trả về file hits kèm thông tin repo chứa file đó.
- Qualifier hỗ trợ (tóm tắt)
- Repository search hỗ trợ qualifiers repo-level: stars:, language:, pushed:, size:, archived:, fork:, in:readme/in:description/topic:…
- Code search hỗ trợ filename:, path:, extension:, content token, repo: (để giới hạn scope). Một số qualifier repo-level không hoạt động trong code search.
- Cách index và timing lag
- Repository metadata cập nhật nhanh hơn. Trường như stars, pushed_at thường cập nhật gần như tức thì.
- Code search dựa trên chỉ mục nội dung mã nguồn phân tán, có độ trễ không cố định. File mới thêm hoặc vừa bị xóa có thể chưa hiện trong code search trong vài giây tới vài phút, đôi khi lâu hơn.
- Hậu quả khi ghép qualifier sai
- Nếu bạn ghép qualifier file-level vào repository search hoặc ngược lại, truy vấn sẽ không trả kết quả bạn mong. Ví dụ đặt filename:requirements.txt và mong repository search trả repo có file đó là sai lầm.
- Viết filename:requirements.txt filename:Dockerfile trong một truy vấn code search cũng sai mục đích khi bạn muốn repo có cả hai file. Code search áp qualifiers trên cùng một file, nên nhiều filename trong cùng query thường có nghĩa AND trên cùng file, chứ không phải "repo chứa cả hai". Kết quả là bạn dễ bỏ sót repo đúng yêu cầu.
- Tại sao cần multi-phase pipeline
- Vì mỗi loại search trả thông tin khác nhau và có giới hạn riêng, pipeline nên: (1) chạy repository search để lọc metadata và readme; (2) chạy code search riêng cho từng filename hoặc nội dung cần; (3) chuẩn hóa và hợp nhất theo full_name của repo; (4) kiểm tra bổ sung (contents API, git trees) để bù đắp index lag và tránh false positive/negative. Cách này đảm bảo bạn không dựa vào một nguồn chỉ mục duy nhất và giảm rủi ro bỏ sót hoặc dính rác.
Thiết kế pipeline nhiều pha: luồng cao và lý do từng pha cần có
Mục tiêu của pipeline này là thu một danh sách các repo GitHub thỏa một tập điều kiện phức hợp và xuất ra file có cờ chấp nhận/loại bỏ kèm lý do. Luồng cao gồm các pha rõ ràng để giảm rủi ro bỏ sót và hạn chế kết quả rác. Dưới đây mô tả step-by-step, lý do cho từng pha, và các checkpoints audit để tránh mất mát khi hợp nhất repository search và code search.
- Phase A — Repository search (metadata + in:readme)
- Hành động: chạy truy vấn repository search (in:readme OR description OR topics) với qualifiers repo-level như stars, language, pushed, archived, size, fork:false nếu muốn loại fork sớm.
- Lý do: nhanh, ít tốn API, thu candidate dựa trên README và metadata. Giúp giảm scope trước khi làm code search tốn kém.
- Checkpoint audit: lưu query text, totalcount trả về, và nếu totalcount > threshold (ví dụ 900) đánh dấu cần phân mảnh. Ghi log bucket hóa nếu phải chia query.
- Phase B — Phân mảnh repository queries khi cần
- Hành động: nếu total_count vượt giới hạn an toàn, tách truy vấn theo buckets an toàn (stars ranges, pushed date ranges, owner alphabet ranges). Chạy từng bucket rồi gộp kết quả.
- Lý do: tránh bị giới hạn trả về (API cap 1000 per query) và đảm bảo coverage. Phân mảnh theo trường monotonic (stars, pushed) an toàn hơn theo text.
- Checkpoint audit: lưu metadata per-bucket, totalcount mỗi bucket, pages fetched, và note buckets có totalcount vượt giới hạn lại.
- Phase C — Code search per-filename
- Hành động: với mỗi filename cần kiểm (ví dụ filename:requirements.txt, filename:Dockerfile) chạy một truy vấn code search riêng, phân trang đầy đủ, thu repository.full_name từ mỗi hit.
- Lý do: code search hoạt động ở cấp file. Để kiểm yêu cầu "có cả A và B", phải chạy từng filename riêng rồi giao nhau theo repo. Không ghép nhiều filename trong một query file-level.
- Checkpoint audit: lưu query per-filename, totalcount, pages, và set(repos) thu được. Nếu code-search trả totalcount lớn, áp chiến lược phân mảnh chuyên biệt (thêm language, path, date) và log lý do phân mảnh.
- Phase D — Hợp nhất theo full_name
- Hành động: chuẩn hóa key bằng fullname lowercase. Tạo tập candidates = union/ intersection tùy yêu cầu: thường là (reposfromreposearch) ∩ (reposfromfilename1) ∩ (reposfromfilename2).
- Lý do: đây là bước quan trọng nhất. Mapping file hits sang repo và merge với repo-level candidates là nơi dễ xảy ra false negative/positive nếu không checkpoint.
- Checkpoint audit: record counts trước và sau merge, xuất sample diff (ví dụ top-50 repo chỉ có trong code search nhưng không trong repo search) để rà soát index lag hoặc phân trang thiếu.
- Phase E — Hậu xử lý và loại trùng
- Hành động: dedupe, gọi repository endpoint để fetch fork, archived, language, stargazerscount, size, pushedat. Nếu muốn loại fork, filter fork:true ở bước này và ghi lý do. Loại mirror bằng mirror_url nếu có, hoặc heuristics.
- Lý do: xác nhận metadata chính xác, loại bỏ mirror/fork theo policy, tránh dựa vào dữ liệu file-level thiếu fork flag.
- Checkpoint audit: export list bị loại với lý do tường minh để review.
- Phase F — Kiểm tồn tại file thực và kiểm chứng nội dung (optional but recommended)
- Hành động: với danh sách candidate cuối, kiểm tra existence chặt bằng Contents API hoặc git/trees recursive trên default branch; nếu cần, check top N branches. Lấy nội dung file để xác minh tokens (ví dụ requirements chứa == hoặc Dockerfile có FROM).
- Lý do: bù cho index lag và biến thể tên file; tránh false positive do code search index lỗi.
- Checkpoint audit: log re-checks, file paths xác thực, trường hợp phải re-try do index lag.
- Phase G — Áp rubric, gán nhãn pass/reject và xuất CSV/JSON
- Hành động: đánh giá từng repo theo rubric gồm mandatory checks (archived false, files present) và scoring items (stars, pushed recency, size, language match). Gán pass/trượt và list fail_reasons chuẩn.
- Lý do: minh bạch quyết định, dễ audit, cho đồng đội lý do rõ ràng.
- Checkpoint audit: lưu score breakdown per repo, lưu mẫu records để kiểm tra chất lượng.
- Phase H — Kiểm soát chất lượng và feedback loop
- Hành động: sample ngẫu nhiên N repo từ các lớp (pass, reject, borderline) để kiểm tra thủ công; monitor metrics: recall proxy (fraction reposfromrepo_search also had file hits), queries hitting >1000, rate limit events.
- Lý do: phát hiện sớm lỗi phân mảnh, index lag, hay mapping sai.
- Checkpoint audit: generate run report với queries, buckets, pages fetched, API quota used, samples for manual review, và anomalies list.
Ghi chú vận hành ngắn
- Luôn checkpoint sau mỗi trang và mỗi bucket để resume nếu rate-limited.
- Ghi rõ full trace: query text → bucket id → page → repo ids → file hits → decisions. File CSV/JSON cuối phải chứa cột fail_reasons chi tiết để audit.
Phân rã truy vấn ví dụ thành nhóm tiêu chí: nội dung, siêu dữ liệu repo, hiện diện file
Đoạn này tách truy vấn mẫu thành ba nhóm tiêu chí rõ ràng để thiết kế pipeline nhiều pha. Mục tiêu: từ truy vấn tổng hợp "in:readme (OR keywords) + language:Python + stars/size/pushed/archived" tạo ra một truy vấn repository-level cho lọc metadata và readme, và các truy vấn code-level riêng cho filename:requirements.txt và filename:Dockerfile. Sau đó mình trình bày cách ghép giao nhau theo full_name repo.
- Nhóm tiêu chí nội dung (content-level)
- Mục tiêu: tìm repo whose README hoặc description chứa các từ khoá quan tâm. Dùng repository search với in:readme, in:description, topic:…
- Ví dụ từ khoá OR an toàn nhóm (phải có ngoặc): ( "machine learning" OR "deep learning" OR "model serving" )
- Ghi chú: đặt ngoặc để đảm bảo OR được áp đúng.
- Nhóm siêu dữ liệu repo (repo-level metadata)
- Các qualifier áp dụng ở cấp repo: language, stars, size, pushed, archived, fork
- Những qualifier này chỉ hợp lệ trong search/repositories, không phải trong search/code.
- Nhóm hiện diện file (file-level)
- Những yêu cầu kiểu filename thuộc về code search vì file là đơn vị tài liệu được index.
- Không ghép nhiều filename trong cùng một truy vấn file-level nếu mục tiêu là "repo có cả file A và file B". Thay vào đó chạy từng truy vấn filename riêng rồi giao nhau theo full_name.
Truy vấn repository-level (sẵn sàng dán vào web UI hoặc REST API, chú ý URL-encode khi gọi API)
- Mẫu an toàn:
in:readme ( "machine learning" OR "deep learning" OR "model serving" ) language:Python stars:>50 size:<100000 pushed:>2022-01-01 archived:false fork:false
- Giải thích ngắn:
- in:readme (…) nhóm OR các cụm từ tìm trong README.
- language:Python lọc repo có Python là ngôn ngữ chính.
- stars, size, pushed, archived, fork là bộ lọc metadata; điều chỉnh giá trị theo mục tiêu của bạn.
- Khi gọi REST API: URL-encode toàn bộ query và dùng perpage=100; kiểm tra totalcount và header Link để quyết định phân mảnh nếu cần.
Truy vấn code-level cho file presence
- Mẫu cho requirements.txt:
q = ‘filename:requirements.txt’ Endpoint REST: GET https://api.github.com/search/code?q=filename:requirements.txt&per_page=100
- Mẫu cho Dockerfile:
q = ‘filename:Dockerfile’ Endpoint REST: GET https://api.github.com/search/code?q=filename:Dockerfile&per_page=100
- Lưu ý quan trọng:
- Chạy mỗi filename thành một truy vấn riêng. Lấy tất cả trang kết quả và extract repository.full_name từ mỗi item.
- Để yêu cầu repo có cả hai file, thực hiện intersection sets theo fullname: reposfinal = reposfromreposearch ∩ reposwithrequirements ∩ reposwith_Dockerfile.
Kiểm nhanh trên web để hiệu chỉnh từ khóa
- Dán truy vấn repository-level vào thanh tìm kiếm GitHub; xem số kết quả và vài sample kết quả trên trang đầu.
- Nếu count quá lớn (thấy nhiều trang), thêm qualifier tách nhỏ như stars:>500 hoặc pushed:>2023-01-01; nếu quá nhỏ, giảm ngưỡng stars hoặc bỏ qualifier size.
- Thử từng truy vấn code-level đơn lẻ trên web để kiểm tra biến thể tên file phổ biến (requirements-dev.txt, Dockerfile.dev). Nếu thấy nhiều biến thể, thêm truy vấn cho các biến thể đó.
Ghi chú ghép kết quả và kiểm tra index lag
- Khi hợp nhất, luôn chuẩn hóa full_name bằng lowercase và dedupe.
- Nếu một repo xuất hiện trong repo-level set nhưng không thấy file-level match, khả năng có index lag. Với repo đã được push gần đây, queue re-check sau vài phút thay vì loại ngay.
- Nếu code search trả quá nhiều hits cho một filename (tổng_count lớn), phân mảnh query theo language, path hoặc created/pushed windows để tránh cap 1000 result.
Tóm tắt bước ngắn gọn để triển khai:
- Chạy repository search bằng truy vấn mẫu trên, phân mảnh nếu totalcount quá lớn. Lưu set repocandidates.
- Chạy code search cho filename:requirements.txt và filename:Dockerfile, thu sets repoperfile.
- Intersect theo fullname: finalcandidates = repocandidates ∩ setreq ∩ set_docker.
- Hậu xử lý: gọi GET /repos/{owner}/{repo} để xác nhận fork/archived/language/stars/size/pushed; loại fork nếu policy yêu cầu; kiểm tra README tồn tại; nếu cần xác minh file thật, dùng /repos/{owner}/{repo}/contents/{path} hoặc git/trees trên default branch.
- Ghi kết quả với cột flags hasrequirements, hasdockerfile, fail_reasons và xuất CSV.
Đây là phân rã truy vấn mẫu thành các truy vấn repo-level và file-level riêng để bạn triển khai pipeline nhiều pha, tránh ghép qualifiers sai và tối ưu hóa kiểm tra tính tồn tại file.
Viết lại truy vấn repository search cho in:readme với nhóm từ khóa OR và qualifiers
Dưới đây là các truy vấn mẫu an toàn để bạn dùng khi muốn tìm repo có keyword trong README, kết hợp nhóm từ khóa bằng OR, và kèm các qualifier metadata như stars, size, pushed, archived, language. Mình cho cả hai dạng ready-to-run: để dán vào thanh tìm kiếm web GitHub và để gọi REST API (URL-encoded). Mỗi qualifier được giải thích, và mình nêu cách tinh chỉnh khi kết quả quá rộng hoặc quá hẹp.
Mẫu truy vấn an toàn cho web UI (copy-paste vào thanh tìm kiếm GitHub)
in:readme ( "machine learning" OR "deep learning" OR "model serving" ) language:Python stars:>50 pushed:>2022-01-01 size:<100000 archived:false fork:false
Giải thích từng phần
- in:readme: tìm các từ trong nội dung README. Dùng khi bạn muốn dự án mô tả chức năng ở README.
- ( "machine learning" OR "deep learning" OR "model serving" ): nhóm OR phải nằm trong ngoặc. Các cụm có dấu cách cần dấu nháy kép để gọi là phrase. Nhóm này tìm README chứa ít nhất một cụm.
- language:Python: lọc repo có ngôn ngữ chính là Python. Đây là filter cấp repo.
- stars:>50: chỉ lấy repo có >50 sao. Thay giá trị tùy mục tiêu tín nhiệm.
- pushed:>2022-01-01: repo có cập nhật gần hơn ngày này. Dùng để ưu tiên dự án còn hoạt động.
- size:<100000: kích thước repo dưới ngưỡng. Xem lưu ý về đơn vị size phía dưới.
- archived:false: loại repo archived.
- fork:false: bỏ fork ngay từ lúc search nếu bạn muốn tránh forks.
Mẫu gọi REST API (URL-encoded)
curl -H "Authorization: token YOUR_TOKEN" \
"https://api.github.com/search/repositories?q=in%3Areadme+%28%22machine+learning%22+OR+%22deep+learning%22+OR+%22model+serving%22%29+language%3APython+stars%3A%3E50+pushed%3A%3E2022-01-01+size%3A%3C100000+archived%3Afalse+fork%3Afalse&per_page=100"
Lưu ý quan trọng và biến thể khi quá rộng hoặc quá hẹp
- Nếu kết quả quá rộng (ví dụ total_count lớn hoặc bạn thấy nhiều trang), phân mảnh query trước khi thu toàn bộ bằng API. Thường xử lý bằng cách tách theo ranges stars hoặc theo pushed windows. Ví dụ chia stars thành buckets như stars:>1000, 500..1000, 100..499, <100.
- Nếu kết quả quá hẹp, giảm ngưỡng stars hoặc bỏ qualifier size, hoặc mở rộng phrases trong nhóm OR. Ví dụ thay stars:>50 thành stars:>10, hoặc bỏ size qualifier.
- Nếu bạn thấy nhiều kết quả không liên quan do README chỉ nhắc qua, thêm qualifier như topic: hoặc in:description để thắt chặt ý định, hoặc yêu cầu presence của file thực tế bằng code search sau này (xem pipeline tổng thể).
- Về size: trường size ở metadata có ý nghĩa tương đối; trước khi dùng làm ngưỡng quyết định, kiểm tra vài repo mẫu để calibrate đơn vị và phân phối trong corpus của bạn. Nếu không chắc, đừng dùng size làm kill-switch; dùng nó như cảnh báo và thực hiện kiểm tra tiếp theo bằng API contents hoặc tree.
Chiến thuật an toàn khi kết hợp OR và qualifiers
- Luôn nhóm các OR bằng ngoặc, và quote các cụm có dấu cách. Thiếu ngoặc là nguồn lỗi phổ biến dẫn tới truy vấn sai ý.
- Dùng repository search để lọc readme/metadata và dùng code search riêng để kiểm tra presence của filename (ví dụ requirements.txt, Dockerfile). Nếu bạn cần repo chứa cả hai file, chạy hai truy vấn code riêng rồi giao nhau theo full_name. Không cố gắng đặt nhiều filename trong một truy vấn code duy nhất để yêu cầu "có cả hai" vì code search hoạt động cấp file.
- Khi dùng API, URL-encode toàn bộ query, set perpage=100 và đọc totalcount. Nếu total_count vượt ngưỡng an toàn của bạn, kích hoạt logic phân mảnh.
Ví dụ biến thể cho trường hợp bạn muốn mở rộng scope readme (bớt chặt)
in:readme ( "ml" OR "machine learning" OR "deep-learning" OR "model serving" ) language:Python stars:>10 pushed:>2021-01-01 archived:false
Ví dụ nếu bạn ưu tiên strict chất lượng (chặt hơn)
in:readme ( "machine learning" OR "model serving" ) language:Python stars:>100 pushed:>2023-01-01 size:<50000 archived:false fork:false
Tóm tắt kỹ thuật ngắn
- Dùng repository search (in:readme + metadata qualifiers) để tạo candidate set. Dùng code search riêng cho filenames cần có. Luôn URL-encode khi gọi API. Nếu query trả quá nhiều kết quả, phân mảnh theo stars hoặc pushed ranges. Kiểm tra vài mẫu thủ công để hiệu chỉnh thresholds như stars và size.
Viết truy vấn code search riêng cho filename:requirements.txt và filename:Dockerfile
Mô tả ngắn: bạn chạy hai truy vấn code search riêng, một cho mỗi filename, rồi lấy trường repository.fullname từ hits và giao nhau theo fullname để tìm repo chứa cả hai file. Không ghép filename vào cùng một truy vấn vì filename: qualifiers áp dụng trên file-level, không ngầm yêu cầu repo có nhiều file khác nhau.
Truy vấn mẫu (web UI)
- filename:requirements.txt
- filename:Dockerfile
Truy vấn mẫu (REST API curl, URL-encoded, per_page=100)
curl -H "Authorization: token $GITHUB_TOKEN" \
"https://api.github.com/search/code?q=filename%3Arequirements.txt&per_page=100&page=1"
curl -H "Authorization: token $GITHUB_TOKEN" \
"https://api.github.com/search/code?q=filename%3ADockerfile&per_page=100&page=1"
Cách chạy và lưu full_name
- Chạy mỗi truy vấn riêng, phân trang với per_page=100 và page=1..N.
- Từ mỗi response JSON lấy items[].repository.fullname và lưu thành setrequirements và setdockerfile. (Ví dụ JSON path .items[].repository.fullname)
- finalrepos = intersection(setrequirements, set_dockerfile).
Lưu ý wildcard và giới hạn filename qualifier
- Wildcard/regex hỗ trợ hạn chế; nếu repo dùng tên biến thể (requirements-dev.txt, Dockerfile.prod) bạn phải chạy thêm queries cho từng biến thể.
- Search API trả totalcount; nếu totalcount > 1000, truy vấn quá rộng và bạn phải phân mảnh query (ví dụ thêm language: hoặc repo: buckets) để tránh bỏ sót.
- Code search index có độ trễ; nếu repo vừa push file mới, hãy re-check bằng Contents API (/repos/{owner}/{repo}/contents/{path}) trước khi loại bỏ.
Cách hiệu chỉnh nhanh trên giao diện web GitHub
- Mở trang tìm kiếm GitHub, dán truy vấn thử vào ô tìm kiếm. Dùng ngoặc để nhóm OR và nháy kép cho cụm từ có dấu cách. Ví dụ:
- in:readme ( "machine learning" OR "model serving" ) language:Python stars:>50 archived:false
- Đọc ngay số kết quả phía trên. Nếu thấy lớn (hàng nghìn) tức là cần phân mảnh; nếu con số nhỏ, truy vấn có vẻ chặt.
- Dùng thanh Filters bên trái để chỉnh nhanh – language, stars, pushed, license, archived, fork. Thử bật/tắt fork để xem ảnh hưởng.
- Mở vài repo mẫu từ trang kết quả để kiểm tra thực tế: xem README, search trong repo (kí hiệu tìm trong repo), và kiểm tra file mong muốn như requirements.txt hoặc Dockerfile bằng nút Contents.
- Khi truy vấn ổn trên web, chuyển sang gọi API để tự động hóa. Kiểm tra total_count trả về từ /search/repositories; nếu >1000, phải phân mảnh query. Ví dụ gọi đầu tiên bằng curl:
curl -H "Authorization: token $GITHUB_TOKEN" \
"https://api.github.com/search/repositories?q=in:readme+(%22machine%20learning%22%20OR%20%22model%20serving%22)+language:Python+stars:>50&per_page=1"
- Ghi lại truy vấn final và ví dụ sample repo để dùng làm kiểm thử khi triển khai API.
Chiến lược phân mảnh truy vấn repository search khi total_count lớn
Mục tiêu của phần này là giúp bạn phát hiện khi một truy vấn repository search quá rộng và chia nó an toàn thành các truy vấn con sao cho không bỏ sót repo hữu dụng. Mình trình bày các bước phát hiện, ba chiến thuật chính để phân mảnh (stars, pushed date, owner alphabet), cách xây buckets không overlapping, cơ chế traceability và checkpoint để audit, rồi kèm ví dụ truy vấn và mẫu mã hóa thực thi bằng Python.
- Phát hiện truy vấn quá rộng
- Khi bạn gọi Search API, đọc trường totalcount trong response đầu tiên. Nếu totalcount vượt threshold bạn đặt (ví dụ 900), kích hoạt chế độ phân mảnh. Threshold nên nhỏ hơn giới hạn trả về của API; đặt mặc định 900 để phòng trường hợp API giới hạn 1000.
- Ngoài total_count, đọc header Link để biết có rel="last". Nếu API trả rel="last" với số trang lớn, đó là dấu cần xem xét.
- Ghi log ngay: query string, timestamp, totalcount, firstpagesize, reason="totalcount>threshold". Lưu query gốc để traceability.
- Nguyên tắc chung khi phân mảnh
- Chọn một trường metadata stable và monotonic nếu có thể. Trường phải hỗ trợ range qualifier trong repository search. Ví dụ tốt: stars, created, pushed. Tránh phân mảnh theo free-text từ readme vì dễ làm thay đổi semantics.
- Mỗi bucket phải là disjoint hoặc có overlap được kiểm soát kèm bước dedupe sau khi thu. Mình khuyến nghị dùng disjoint ranges để đơn giản: ví dụ stars:>=10000, stars:5000..9999, stars:1000..4999, stars:0..999.
- Ghi metadata về mỗi bucket: bucketid, bucketquery, expectedtotalcount (API trả), pages_fetched. Điều này cho traceability và audit.
- Chiến thuật phân mảnh an toàn
A. Stars buckets – ưu tiên khi stars phù hợp với mục tiêu
- Kịch bản: truy vấn gốc có total_count lớn. Chia theo stars ranges giảm dần để lấy repo có nhiều traction trước.
- Cách tạo buckets:
- Xác định max_star dựa trên sample nhanh: chạy query stars:>X thử với X = 10000, 5000, 1000… để tìm điểm giảm natural.
- Sinh buckets disjoint, ví dụ:
- stars:>=10000
- stars:5000..9999
- stars:1000..4999
- stars:100..999
- stars:0..99
- Mỗi bucket chạy độc lập; nếu một bucket vẫn trả total_count>threshold, tiếp tục phân mảnh bucket đó theo pushed date hoặc thu hẹp range stars trong bucket.
B. Pushed date buckets – khi stars không phân bố hữu ích
- Dùng khi bạn quan tâm tới activity recent hoặc stars không có ý nghĩa.
- Chia theo khoảng thời gian non-overlapping, ví dụ tuần hoặc tháng:
- pushed:>2024-01-01
- pushed:2023-07-01..2023-12-31
- pushed:2023-01-01..2023-06-30
- pushed:<2023-01-01
- Khi dataset quá lớn, giảm window tới tuần hoặc ngày cho phần có density cao.
C. Owner alphabet buckets – khi không thể dùng numeric ranges
- Dùng khi bạn không muốn thay đổi semantics search (ví dụ muốn giữ toàn bộ readme keyword nhưng phải chia do cap).
- Tách theo ký tự đầu owner: repo: a..m và n..z không trực tiếp supported trong qualifier nhưng bạn có thực hiện bằng thêm qualifier user: hoặc run queries repo:owner_prefix nếu bạn có danh sách owners. Thực tế áp dụng:
- Lấy danh sách owner đầu tiên bằng search theo tên owner prefix nếu bạn có nguồn owners.
- Hoặc dùng created/updated date kết hợp.
- Xây buckets không overlapping và traceability
- Luôn tạo buckets disjoint. Nếu dùng stars: a..b và stars: b+1..c thì không overlap.
- Giữ mapping bucket -> query -> response meta trong storage (database hoặc file). Mẫu bản ghi:
- bucketid | query | totalcount | pagesfetched | firstpagetime | completeflag | items_count
- Sau khi thu từng bucket, merge kết quả và dedupe theo fullname normalized (lowercase owner/name). Dedupe cho traceability giữ lại originbucket cho mỗi repo để biết repo nằm trong bucket nào.
- Khi một bucket vẫn quá lớn
- Nếu bucket total_count vẫn > threshold, áp chiến lược phân mảnh lồng: ví dụ trong một stars bucket còn quá lớn, phân mảnh theo pushed date nhỏ hơn. Tránh phân mảnh bằng từ khoá readme vì semantics sẽ thay đổi. Luồng lồng:
- stars bucket -> nếu >threshold -> chia thành pushed date sub-buckets.
- hoặc chia stars bucket tiếp thành two subranges equally sized bằng thử nghiệm binary search: tăng lower bound cho đến khi mỗi subrange <= threshold.
- Hậu kiểm an toàn để tránh bỏ sót
- Sau khi hoàn tất thu buckets, nếu unioncount < totalcount (API gốc) cần investigate: có thể do total_count cap của API hoặc index lag. Ghi log cảnh báo.
- Lấy sample dự phòng: chọn ngẫu nhiên N repo (ví dụ N=50) từ bucket boundaries chưa thu hoặc từ kết quả web UI và kiểm tra presence via /repos/:owner/:repo endpoints hoặc contents API để đảm bảo không bỏ sót loại repo bạn thực sự cần.
- Nếu phát hiện trường hợp index lag (repo mới có file nhưng code search chưa thấy), lập queue re-check sau delay configurable (ví dụ 5-15 phút).
- Tối ưu rate limit và checkpoint
- Mỗi page fetch lưu checkpoint: (bucketid, pagenumber). Nếu pipeline dừng, resume từ checkpoint.
- Tính toán estimate calls: pagesperbucket = ceil(totalcountbucket / per_page). Nếu số calls quá lớn, phân bố chạy theo thời gian hoặc dùng nhiều tokens.
- Sử dụng exponential backoff khi gặp 403/429 và đọc X-RateLimit-Reset header để resume.
- Ví dụ truy vấn và mô tả
- Truy vấn gốc repository search (ví dụ):
in:readme ( "machine learning" OR "model serving" ) language:Python archived:false
- Phát hiện total_count = 25400 -> phân mảnh theo stars. Một bucket ví dụ:
in:readme ( "machine learning" OR "model serving" ) language:Python archived:false stars:>=5000
- Nếu bucket stars:>=5000 trả total_count 1200 -> vẫn >900 -> phân mảnh bucket này theo pushed date:
in:readme ( "machine learning" OR "model serving" ) language:Python archived:false stars:>=5000 pushed:>2023-01-01
- Mẫu pseudo-code Python cho bucket loop
# Ghi chú: đoạn mã là mẫu minh họa, dùng requests hoặc thư viện HTTP bạn chọn.
THRESHOLD = 900
per_page = 100
def search_repos(q, page=1):
# gọi https://api.github.com/search/repositories?q={q}&per_page={per_page}&page={page}
# trả về dict with total_count and items
pass
def collect_bucket(q):
first = search_repos(q, page=1)
total = first['total_count']
if total <= THRESHOLD:
fetch_all_pages(q, total)
return
# nếu vẫn > threshold, chọn field để tách tiếp (stars/pushed)
# ví dụ: phân mảnh theo stars ranges externally generated
buckets = generate_stars_buckets()
for bq in buckets:
collect_bucket(original_query + ' ' + bq)
def fetch_all_pages(q, total):
pages = math.ceil(min(total, 1000) / per_page)
for p in range(1, pages+1):
resp = search_repos(q, page=p)
save_items(resp['items'])
checkpoint_save(q, p)
- Ghi log, audit và lý do phân mảnh
- Mỗi hành động phân mảnh phải ghi: parentquery, childquery, totalcountparent, totalcountchild. Khi xuất CSV cuối cùng, thêm cột source_bucket để trace repo đến query gốc.
- Lưu tất cả queries dùng để tạo kết quả, kèm timestamp và token id nếu dùng multi-token, để audit khi cần chứng minh vì sao một repo được/không được thu thập.
Kết luận tóm tắt kỹ thuật
- Phát hiện bằng totalcount và Link header. Phân mảnh bằng fields monotonic như stars hoặc pushed date. Tạo buckets disjoint, ghi metadata trace cho mỗi bucket. Nếu bucket vẫn quá lớn, lồng phân mảnh. Dedupe theo fullname và luôn checkpoint page-level. Thực hiện sample re-check và dùng Contents API cho các trường hợp nhạy cảm để giảm false negative do index lag.
Chiến lược phân mảnh cho code search và lưu ý không đấu filename vào cùng query
Code search khác repository search vì nó hoạt động ở cấp file: mỗi hit là một file trong repo, không phải một bản tóm tắt repo. Do đó các qualifier filename: áp dụng cho file đang match, không ngầm nói "repo có file A và file B". Nếu bạn viết filename:requirements.txt filename:Dockerfile trong một truy vấn, engine sẽ cố match cả qualifier trên cùng một file, nên gần như vô nghĩa cho mục tiêu "repo chứa cả hai file". Quy tắc đầu tiên: luôn chạy mỗi filename một truy vấn riêng và sau đó intersect kết quả theo repo.full_name.
Chiến lược thực thi an toàn và tiết kiệm rate:
- Thứ tự truy vấn thông minh: chạy trước các filename ít phổ biến (ví dụ Dockerfile) để giảm candidate set nhanh. Lưu tập repo.full_name từ mỗi truy vấn filename, rồi intersect theo yêu cầu AND; nếu chỉ cần OR thì union.
- Khi code search trả quá nhiều hits (total_count lớn hoặc gặp cap), đừng cố lấy mọi page. Thay vào đó:
- Phân đoạn theo repository buckets đã lấy từ repository search (chạy code search chỉ trong tập đó bằng repo:owner/name qualifiers) để giới hạn scope.
- Hoặc sample stratified theo stars/pushed: chọn mẫu tỷ lệ theo buckets popularity để kiểm tra presence file trên các lớp quan tâm.
- Fallback contents-check khi code search không thực tế: dùng Contents API hoặc Git Trees cho từng repo candidate để kiểm tra existence của path cụ thể (GET /repos/{owner}/{repo}/contents/{path} hoặc GET /repos/{owner}/{repo}/git/trees/{branch}?recursive=1). Lợi ích – loại bỏ paging trên code search và cho kết quả xác thực trên branch mặc định; chi phí – một request per check, nên apply chỉ cho candidate shortlist hoặc cho sample.
- Khi hits quá lớn và bạn cần coverage: phân mảnh code search theo language, path hoặc pushed date; hoặc phân đoạn repo candidates bằng stars/created windows rồi chạy filename queries trong từng bucket. Luôn log bucket, pages fetched và dùng checkpoint để resume.
Lấy kết quả REST API: cách gọi search repositories và search code, pagination và xử lý giới hạn
Mục tiêu: minh họa cách gọi hai endpoint search của GitHub REST API, xử lý phân trang an toàn, đọc total_count và header Link, kiểm soát rate limit bằng header, áp retry/backoff và checkpoint để resume khi bị gián đoạn.
- Endpoint mẫu và tham số cơ bản
- Repository search:
GET https://api.github.com/search/repositories?q=in:readme+("machine+learning"+OR+"deep+learning")+language:Python+stars:>50+archived:false&per_page=100&page=1
- Code search (file-level):
GET https://api.github.com/search/code?q=filename:requirements.txt+language:Python&per_page=100&page=1
- Chú ý: per_page tối đa thường là 100. Luôn URL-encode query khi gọi qua HTTP client.
- Đọc total_count và phát hiện cap
- Response JSON có trường total_count. Kiểm tra ngay page 1 để biết tổng kết quả khả dụng.
- Nếu total_count vượt ngưỡng an toàn (ví dụ bạn đặt threshold nội bộ 900) thì cần phân đoạn truy vấn để tránh bị bỏ sót do giới hạn trả về cho một truy vấn.
- Lưu ý: total_count phản ánh số kết quả tìm được theo index; dùng nó để quyết định phân mảnh query.
- Phân trang thực tế và Link header
- Khi response có nhiều page, server trả header Link chứa rel="next" và rel="last". Phân tích header này để biết page tiếp theo và tổng page.
- Luôn lấy perpage=100 khi muốn giảm số gọi. Lặp: request page=1..lastpage hoặc follow rel="next" cho đến khi không còn.
- Ví dụ trích xuất Link:
Link: <https://api.github.com/search/code?q=...&page=2&per_page=100>; rel="next", <https://api.github.com/search/code?q=...&page=10&per_page=100>; rel="last"
- Parse để lấy số last page và lặp hoặc follow rel="next".
- Xử lý giới hạn trả về lớn (cap) và phân mảnh an toàn
- Nếu totalcount > thresholdcap (ví dụ 1000) bạn phải tách query thành sub-queries. Hai chiến lược an toàn:
- Theo metadata range: chia bằng stars, created, hoặc pushed ranges. Ví dụ stars:>=500, stars:100..499, stars:<100.
- Theo thời gian: pushed:>2023-01-01, pushed:2022-01-01..2022-12-31, v.v.
- Mỗi sub-query phải đảm bảo total_count nhỏ hơn threshold để thu toàn bộ pages. Ghi log sub-queries để traceability.
- Rate limit headers, retry và backoff
- Mỗi response chứa header liên quan rate limit. Kiểm tra:
- X-RateLimit-Limit
- X-RateLimit-Remaining
- X-RateLimit-Reset (epoch seconds khi quota reset)
- Khi nhận status 403 hoặc 429, đọc X-RateLimit-Reset để biết khi resume. Áp dụng exponential backoff với jitter khi gặp transient errors:
- initial delay = 1s, multiply by 2 mỗi lần, cap ở 60s, cộng random jitter 0-1000ms.
- Khi Remaining thấp, tự throttle: sleep until safe window or rotate token nếu có nhiều token.
- Checkpointing để resume an toàn
- Sau mỗi page thành công lưu checkpoint nhỏ:
- query identifier, bucket id (nếu phân mảnh), current page, timestamp, etag/last-modified nếu có.
- Lưu checkpoint vào persistent store (file, DB). Khi pipeline khởi động lại, đọc checkpoint và tiếp tục từ page+1 của bucket.
- Ghi log counts per page để phát hiện mất page.
- Xử lý kết quả code search và hợp nhất theo repo
- Code search trả items file-level. Khi thu repo list cho filename, trích item.repository.full_name và chuẩn hóa lowercase.
- Lưu sets per filename rồi thực hiện union/intersection theo yêu cầu.
- Dedupe bằng full_name và, khi cần loại fork, gọi GET /repos/{owner}/{repo} để kiểm tra fork boolean trước khi final.
- Mẫu gọi đơn giản bằng Python (requests) với pagination, backoff và checkpoint
# pseudocode, fill token and persistence
import requests, time, random, json
TOKEN = "ghp_xxx"
HEADERS = {"Authorization": f"token {TOKEN}", "Accept": "application/vnd.github.v3+json"}
def fetch_search(q, page):
url = "https://api.github.com/search/repositories"
params = {"q": q, "per_page": 100, "page": page}
r = requests.get(url, headers=HEADERS, params=params)
return r
# loop with backoff and checkpointing (simplified)
page = load_checkpoint() or 1
while True:
r = fetch_search(query, page)
if r.status_code == 200:
data = r.json()
process_items(data["items"])
save_checkpoint(page)
link = r.headers.get("Link","")
if 'rel="next"' in link:
page += 1
continue
break
elif r.status_code in (403,429):
reset = int(r.headers.get("X-RateLimit-Reset", time.time()+60))
sleep = max(reset - time.time(), 1)
time.sleep(sleep + random.random())
else:
# transient retry with backoff
for t in [1,2,4,8]:
time.sleep(t + random.random())
r = fetch_search(query, page)
if r.status_code == 200:
break
else:
raise RuntimeError("failed after retries")
- Ghi chú vận hành và kiểm tra
- Log mỗi query và total_count, pages fetched, và mọi sub-queries khi phân mảnh. Giữ trace để audit thiếu hụt.
- Kiểm thử nhỏ: chạy query prototype trên web để ước lượng total_count và điều chỉnh phân mảnh trước khi scale.
- Nếu pipeline tốn nhiều calls, cân nhắc: chạy code search chỉ cho candidate repo từ repository search bằng repo: qualifier để giảm phạm vi, hoặc dùng Contents API cho kiểm tra file existence từng repo.
Kết luận: thực hiện pagination đúng, kiểm tra total_count để phát hiện khi cần phân mảnh, dùng Link header để phân trang, đọc X-RateLimit-* để quyết định retry hoặc sleep, và lưu checkpoint sau mỗi page để resume an toàn. Thiết kế sub-query theo metadata range khi một truy vấn quá rộng để tránh bỏ sót kết quả.
Chuẩn hóa và hợp nhất dữ liệu theo full_name
Mở đầu ngắn: tại bước hợp nhất, key duy nhất để join là repo full_name dưới dạng owner/name. Mình khuyến nghị một quy ước rõ ràng và các bước xử lý để tránh mismatch do chữ hoa, encoding hay khác biệt trả về giữa repository search và code search.
- Quy ước chuẩn hóa
- Luôn lưu full_name chuẩn hóa dạng lowercase, không khoảng trắng thừa, và chuẩn Unicode NFKC. Ví dụ: "Owner/Repo-Name" -> "owner/repo-name".
- Loại bỏ BOM và trim hai đầu chuỗi. Thay dấu backslash hoặc multiple slashes bằng single slash nếu vô tình có.
- Cách extract từ API responses
- Repository search items: dùng field repository.fullname hoặc item.fullname từ /search/repositories.
- Code search items: mỗi file hit trả item.repository.full_name trong v3 API. Khi dùng GraphQL, map repository.nameWithOwner.
- Nếu một response không cung cấp full_name trực tiếp, compose bằng owner.login + "/" + repo.name.
- Dedupe và merge
- Sau thu thập, map mỗi record vào dictionary keyed by normalizedfullname.
- Khi gặp nhiều nguồn cho cùng fullname, merge theo rule: keep latest metadata fields by pushedat, union boolean flags (hasrequirements, hasdockerfile), aggregate lists (reasons, query_sources).
- Ghi provenance – lưu which query and which API page tạo entry đầu tiên.
- Xử lý encoding và ký tự đặc biệt
- Trước khi normalize, apply Unicode NFKC normalization để hợp nhất chữ hoa/nhỏ và ký tự kết hợp.
- Nếu owner hoặc repo name chứa percent-encoding trong web UI, decode percent-encoding trước khi lowercasing.
- Đối với ký tự không chuẩn (ví dụ unicode lookalikes), chỉ normalise; không tự động replace ký tự bằng ASCII vì owner name thực có thể chứa unicode.
- Kiểm nghiệm nhanh
- Sau hợp nhất, kiểm tra counts: number of unique normalizedfullname phải bằng số dòng output. Nếu giảm đột ngột so với expectation, log sample 50 raw names để debug mismatch.
Kết quả bạn phải giao: một bảng/dataset keyed bằng full_name (lowercase, NFKC) kèm cột cờ cho mỗi filename tìm được, nguồn truy vấn, thời gian lấy và danh sách lý do/nhãn cho accept/reject.
Hậu xử lý: loại fork, mirror, archived và cách tùy chọn giữ lại fork
Mình trình bày một quy trình hậu xử lý rõ ràng để xử lý fork, mirror và archived, kèm chính sách auto-remove, auto-keep và review-queue cùng lý do cho mỗi trường hợp.
- Thu thập metadata cơ bản (rẻ)
- Gọi GET /repos/{owner}/{repo}. Lấy fields: fork (boolean), mirrorurl, archived, stargazerscount, pushed_at, size, parent/source nếu có.
- Quy tắc loại mirror
- Nếu mirror_url != null -> auto-remove. Lý do: mirror thường chỉ sao chép từ nguồn khác; giữ sẽ sinh trùng lặp.
- Quy tắc xử lý archived
- Nếu archived == true -> default là auto-remove. Nếu mục tiêu là snapshot hoặc nghiên cứu lịch sử, chuyển vào review-queue và gắn tag reason="archived".
- Quy tắc xử lý fork
- Bước nhanh sàng lọc:
- Nếu fork == true và stargazerscount <= 3 và pushedat < 365 ngày và forkscount <= 2 -> candidateauto_remove.
- Nếu fork == true và (stargazerscount >= 10 OR forkscount > 5 OR releasescount > 0) -> candidateauto_keep.
- Các trường hợp còn lại -> candidate_review.
- Lý do: stars/forks/releases là chỉ báo cộng đồng; pushed_at phản ánh hoạt động.
- Kiểm tra phát triển độc lập (chi tiết, tốn API)
- Với candidatereview hoặc candidateauto_remove có nghi ngờ, so sánh commit head:
- Lấy default branch sha của fork và của parent/source.
- Nếu sha equal -> identical, an toàn để remove.
- Nếu not equal then call GET /repos/{owner}/{repo}/compare/{parentdefault}…{forkdefault}. Nếu aheadby > 0 hoặc status != identical -> fork phát triển độc lập -> promote to autokeep hoặc review manual depending on stars.
- Lý do: so sánh commit xác định xem fork có thay đổi độc lập hay chỉ là bản sao.
- Whitelist owners và policy audit
- Dự trữ danh sách owners/org whitelist; repos của họ không auto-remove even if fork==true.
- Luôn lưu lý do quyết định và provenance: which checks passed, timestamps, API responses.
- Hành động cuối và ghi nhãn
- Auto-remove: remove from final list; ghi reason như "mirror", "fork-identical", "archived".
- Auto-keep: giữ trong list; ghi reason như "fork-active", "releases", "stars>=10".
- Review-queue: xuất CSV riêng gồm fullname, reasonflags, links to compare URL, và hướng dẫn human check (e.g., inspect commits, check releases).
Thực thi theo thứ tự: metadata filter -> cheap heuristics -> compare commit cho nghi ngờ -> quyết định. Luôn cache parent sha và throttle compare calls để tránh vượt rate limit.
Kiểm tồn tại README và ánh xạ ngôn ngữ repo
Mình trình bày bước hậu xử lý đơn lẻ bạn áp lên mỗi repo candidate để (1) xác nhận có README hợp lệ và (2) đánh giá mức độ "Python" của repo bằng languages API. Kết quả dùng để gán cờ và lý do trong CSV (ví dụ hasreadme, readmelen, languageprimary, pythonbytespct, pythonfile_evidence).
- Kiểm README bằng REST API
- Gọi: GET https://api.github.com/repos/:owner/:repo/readme
- Trường trả về quan trọng: content (base64), encoding, path, sha.
- Hành động:
- Nếu response 200: decode base64 content, strip whitespace, lấy độ dài ký tự. Ví dụ ngưỡng tối thiểu: 80 ký tự; nếu <80, mark fail reason "readmetooshort".
- Nếu 404: mark "no_readme".
- Ví dụ curl (thay OWNER/REPO và TOKEN):
curl -s -H "Authorization: token $GITHUB_TOKEN" \
https://api.github.com/repos/OWNER/REPO/readme \
| jq -r '.content' | base64 --decode > temp_readme.md
wc -c temp_readme.md
- Ghi lại readmelen (số ký tự) và readmepath.
- Ánh xạ ngôn ngữ bằng Languages API
- Gọi: GET https://api.github.com/repos/:owner/:repo/languages
- Response là object mapping language -> bytes. Tính tổng bytes and share của Python:
- pythonbytespct = languages["Python"] / sum(all bytes) * 100
- language_primary = language có bytes lớn nhất (first key by value)
- Quy tắc đề xuất để automatic accept là:
- Nếu languageprimary == "Python" OR pythonbytespct >= 30 => mark "pythonprimary" (high confidence).
- Nếu 5% <= pythonbytespct < 30 => mark "python_partial" (needs check).
- Nếu pythonbytespct < 5% và languageprimary != "Python" => mark "pythonabsent".
- Nếu primary language khác nhưng có file Python
- Kiểm chứng file-level evidence (ít tốn hoặc tốn thêm calls):
- Tùy chọn A (tối ưu rate): gọi code search per-repo cho *.py không phải luôn cần, thay bằng check tồn tại file manifest: GET /repos/:owner/:repo/contents/{path} cho paths phổ biến như requirements.txt, setup.py, pyproject.toml.
- Tùy chọn B (chắc chắn hơn): GET https://api.github.com/search/code?q=repo:OWNER/REPO+extension:py và lấy count; nếu count>0 => mark "pythonfilespresent".
- Quy tắc kết hợp:
- Nếu languageprimary != "Python" nhưng (requirements.txt or setup.py or pyproject.toml exists) OR pythonfilespresent => mark "pythonevidence" với confidence cao hơn.
- Nếu không có evidence, treat as non-Python.
- Ghi nhãn output (CSV/JSON)
- Các trường gợi ý: fullname, hasreadme (true/false), readmelen, readmepath, languageprimary, pythonbytespct (float), pythonfileevidence (true/false), pythonstatus (pythonprimary | pythonpartial | pythonevidence | pythonabsent), notes.
- Ví dụ lý do loại: "noreadme", "readmetooshort", "languageabsent", "pythonpartialneedsmanualreview".
- Vận hành và lưu ý thực tế
- Cache languages và readme results một thời gian (ví dụ 24h) để tránh gọi lại nhiều lần.
- Nếu repo mới pushed gần đây và code search không thấy file bạn cần, cân nhắc re-check sau 5-10 phút do index lag.
- Nếu bạn cần tuyệt đối chắc chắn về presence của file trên branch non-default, gọi GET /repos/:owner/:repo/branches để lấy branch list rồi kiểm tra contents trên branch đó; đây tốn thêm API calls, chỉ làm cho candidate lọt vào shortlist.
Kết thúc phần: sau bước này, mỗi repo có cờ readme và đánh giá Python rõ ràng để dùng trong rubric tổng thể và cột lý do của file CSV xuất ra.
Áp bộ lọc siêu dữ liệu sau hợp nhất và gán nhãn pass/trượt cho từng tiêu chí
Mình trình bày một checklist thực thi để áp bộ lọc metadata lên tập repo đã hợp nhất (kết quả repository search giao với code search), rồi gán nhãn pass hoặc fail cho từng tiêu chí. Mỗi repo phải được gán một hoặc nhiều reason token để xuất vào cột fail_reasons trong CSV. Các bước dưới đây là thao tác tự động trong pipeline hậu xử lý.
- Chuẩn hóa key
- Key join: full_name lowercased (owner/name). Dùng làm khóa duy nhất trước khi đánh kiểm tra metadata.
- Trường metadata cần kiểm
- stars (stargazers_count)
- size (repo.size) — trước khi dùng, xác minh đơn vị qua một sample; treat thresholds như heuristic
- pushed_at (thời điểm push mới nhất)
- archived (boolean)
- languageprimary (repository.language) và pythonbytes_pct từ /languages nếu cần
- Quy tắc pass/fail cho từng criterion (mẫu ngưỡng đề xuất, bạn chỉnh theo nhu cầu)
- archived
- pass: archived == false
- fail token: archived
- stars
- pass: stargazers_count >= 50
- marginal (manual review): 10 <= stargazerscount < 50 -> token starslow
- fail: stargazerscount < 10 -> token starstoo_low
- size
- action: trước tiên sample vài repo để xác minh unit; dùng threshold tương đối
- pass: size <= 200000 (heuristic) -> token size_ok
- warn/review: 200000 < size <= 1000000 -> token sizelargereview
- fail: size > 1000000 -> token sizetoolarge
- pushed_at (recency)
- pass: pushedat within last 12 months -> token recentpush
- review: 12–36 months -> token stale_medium
- fail: >36 months -> token stale_old
- language
- pass: languageprimary == "Python" OR pythonbytespct >= 30 -> token languagematch
- review: 5% <= pythonbytespct < 30 -> token language_partial
- fail: pythonbytespct < 5% -> token language_mismatch
- Logic tổng hợp quyết định
- Tiêu chí bắt buộc (hard-fail): archived -> immediate fail; nếu bạn muốn loại fork ở đây thì thêm fork:true -> token fork_excluded.
- Nếu có bất kỳ hard-fail token nào, pass_overall = false.
- Nếu không hard-fail, tính score từ các criterion (ví dụ stars: 0-3 pts, pushed: 0-2, size: 0-1, language: 0-2). Nếu tổng điểm >= thresholdaccept (ví dụ 4) -> passoverall = true, else pass_overall = false and tag reason tokens thu được.
- Nếu repo thiếu evidence file-level (ví dụ code search đã trả hit nhưng tree check không xác nhận) thì thêm token indexlagor_missing, đặt into manual review bucket rather than final reject.
- Cú pháp token và cấu trúc CSV
- Cột bắt buộc: fullname | htmlurl | stars | size | pushedat | languageprimary | pythonbytespct | hasrequirements | hasdockerfile | passoverall | failreasons
- fail_reasons là chuỗi phân tách bằng dấu chấm phẩy, ví dụ:
- "archived;starstoolow;language_mismatch"
- hoặc "missingDockerfile;sizelarge_review"
- Gợi ý: thêm cột debugqueries để lưu truy vấn reposearch/code_search đã sinh cho audit.
- Kiểm soát an toàn trước khi reject
- Trước khi reject vì size hoặc stars thấp, nếu repo có hasrequirements == true OR hasdockerfile == true OR readmelen >= 200 thì đẩy vào manualreview thay vì auto-reject.
- Nếu pushedat gần đây nhưng code search không thấy file đã mong đợi, thêm token indexlag_suspected và queue recheck sau 5–15 phút.
- Ghi log và traceability
- Với mỗi repo, log: applied thresholds, tokens assigned, timestamp, and source queries. Điều này cần trong CSV audit hoặc file log đi kèm.
Áp checklist này vào bước hậu xử lý của pipeline; điều chỉnh ngưỡng theo dữ liệu mẫu của bạn và luôn giữ manual_review bucket cho các trường hợp biên.
Thiết kế rubric đánh giá và loại bỏ: tiêu chí bắt buộc, tiêu chí chấm điểm, ngưỡng quyết định
Mục tiêu: đưa ra một rubric tự động để gán pass/reject/manual-review cho từng repo sau khi đã hợp nhất repository-search và code-search. Mình trình bày: (1) các kiểm tra bắt buộc; (2) các mục chấm điểm với điểm và trọng số; (3) quy tắc quyết định; (4) mẫu lý do tự động để ghi vào cột fail_reasons.
- Kiểm tra bắt buộc (hard-fail nếu không thỏa)
- archived == false => nếu true thì fail token "archived"
- repository.private == false => nếu true thì fail token "private_repo"
- hasrequirements OR hasdockerfile tùy policy: nếu bộ tiêu chí yêu cầu cả hai nhưng thiếu file nào thì fail token "missingrequirements" hoặc "missingDockerfile"
- fork handling: nếu policy đòi không có fork thì fork == false, else annotate is_fork true và tiếp tục
- Mục chấm điểm (scoring items)
- stars: >=500 -> 3 điểm; 100–499 -> 2; 10–99 -> 1; <10 -> 0
- pushed recency: <6 tháng -> 2; 6–12 tháng -> 1; >12 tháng -> 0
- language match: primary == Python OR pythonbytespct >=30% -> 2; 10–30% ->1; else 0
- README quality: exists and len >=200 chars -> 1; else 0
- size: <=50MB ->1; 50–200MB ->0; >200MB -> -1 (penalty)
- file evidence: each required file present (requirements.txt, Dockerfile) -> +2 mỗi file
- Quy tắc tổng hợp và ngưỡng
- Tính total_score = sum(points) + penalties
- Mandatory checks must pass; nếu bất kỳ hard-fail thì final = REJECT
- Nếu mandatory pass:
- total_score >= 5 => ACCEPT (auto)
- totalscore 3–4 => MANUALREVIEW (uy tín trung bình)
- total_score <=2 => REJECT (auto)
- Ngoài ra: nếu repo vừa pushedat very recent (<1 giờ) nhưng codesearch thiếu file, gắn token "indexlagsuspected" và đặt vào MANUAL_REVIEW thay vì REJECT.
- Cách ghi lý do tự động (fail_reasons)
- Tạo list token khi kiểm tra từng mục; ví dụ:
- archived -> "archived"
- missing Dockerfile -> "missing_Dockerfile"
- stars low -> "stars<10"
- language mismatch -> "python_bytes<10%"
- size large -> "size>200MB"
- index lag -> "indexlagsuspected"
- Combine tokens thành chuỗi phân tách bằng dấu chấm phẩy, ví dụ "missing_Dockerfile;stars<10;size>200MB"
- Ví dụ cụ thể
- Repo A: archived=false, stars=120, pushed=3 tháng, pythonbytes=40%, hasrequirements=true, has_dockerfile=true, size=10MB
- points: stars 2 + pushed 2 + language 2 + readme 1 + files 4 = 11 -> ACCEPT
- fail_reasons = ""
- Repo B: archived=false, stars=2, pushed=26 tháng, pythonbytes=2%, hasrequirements=false, has_dockerfile=true, size=300MB
- mandatory pass; points: stars0+pushed0+language0+readme0+file(1)=1; penalty size -1 => total 0 -> REJECT
- failreasons = "missingrequirements;stars<10;python_bytes<10%;size>200MB"
Ghi chú vận hành: lưu tokens chi tiết cho audit; giữ manualreview list để rà tay cho các case trung tính hoặc indexlag.
Quy ước đầu ra CSV/JSON: trường bắt buộc và ví dụ lý do loại bỏ có thể sinh tự động
Định nghĩa cột (ngắn gọn)
- full_name: owner/repo (chuẩn hóa lowercase).
- html_url: liên kết GitHub tới repo.
- stars: số stargazers hiện tại (integer).
- size: kích thước repo theo GitHub metadata (sử dụng như chỉ báo; kiểm tra thêm khi cần).
- pushed_at: ISO8601 timestamp của lần push gần nhất.
- language: primary language (repository.language).
- has_requirements: true nếu tồn tại file requirements.txt (sau kiểm contents/tree).
- has_dockerfile: true nếu tồn tại Dockerfile (sau kiểm contents/tree).
- fork: boolean (repo.fork).
- archived: boolean (repo.archived).
- score: numeric tổng theo rubric (int hoặc float).
- pass_bool: true nếu tổng thể đạt ngưỡng accept.
- fail_reasons: danh sách token lý do loại hoặc flags, phân tách bằng dấu chấm phẩy.
Mẫu dòng CSV (header + một dòng mẫu) fullname,htmlurl,stars,size,pushedat,language,hasrequirements,hasdockerfile,fork,archived,score,passbool,fail_reasons acme/example-repo,https://github.com/acme/example-repo,128,10240,2025-06-01T12:34:56Z,Python,true,true,false,false,9,true,
Mẫu JSON tương ứng (một object) { "fullname": "acme/example-repo", "htmlurl": "https://github.com/acme/example-repo", "stars": 128, "size": 10240, "pushedat": "2025-06-01T12:34:56Z", "language": "Python", "hasrequirements": true, "hasdockerfile": true, "fork": false, "archived": false, "score": 9, "passbool": true, "fail_reasons": "" }
Ví dụ lý do loại tự động (các token phổ biến)
- archived ; fork ; missingrequirements ; missingdockerfile ; stars<10 ; pythonbytes<10% ; size>200MB ; indexlag_suspected
Ghi chú vận hành ngắn: generate failreasons bằng rules engine sau mỗi kiểm tra; giữ fullname làm khóa để trace và audit.
Kiểm soát chất lượng và tránh bẫy thường gặp
Những bẫy phổ biến và cách né
- OR và dấu ngoặc: luôn nhóm các biểu thức OR bằng ngoặc tròn. Nếu không, query có thể được hiểu sai và trả kết quả quá rộng hoặc quá hẹp. Ví dụ: in:readme ( "machine learning" OR "deep learning" ) language:Python.
- Cụm từ có dấu cách: đặt trong dấu nháy kép. Ví dụ "model serving". Thiếu nháy sẽ thành hai token AND, làm lệch kết quả.
- Filename qualifiers ở cấp file: filename: qualifier là file-level. Nhiều filename trong một query file-level không nghĩa là "repo có cả hai file". Để yêu cầu repo chứa cả requirements.txt và Dockerfile, chạy hai truy vấn code riêng rồi giao nhau theo full_name.
- Index lag (độ trễ lập chỉ mục): code search và repository metadata cập nhật không đồng bộ. Nếu repo vừa push file, chờ vài phút rồi re-check bằng contents API hoặc git trees trước khi reject.
- 1000-result cap: Search API có giới hạn trả về cho một truy vấn. Luôn kiểm tra total_count; nếu vượt ngưỡng, phân đoạn query an toàn theo range (stars, pushed date, owner alphabet) và hợp nhất kết quả. Lưu logs của các buckets để audit.
- Khác biệt web vs API: web UI có thể hiển thị mặt trận tổng hợp, API trả file-level items. Khi tự động hoá, tin vào API và test tương thích cú pháp. Prototype trên web nhưng xác thực bằng API.
- Rate limits và retries: đọc header X-RateLimit-*, dùng exponential backoff với jitter, checkpoint per-page để resume, và đa token hoặc phân phối chạy nếu lượng lớn.
Checklist kiểm tra chất lượng trước khi khóa danh sách
- [ ] Query syntax kiểm (ngoặc, nháy, URL-encode khi gọi API).
- [ ] Kiểm tra total_count trên response đầu; nếu > threshold, bật phân đoạn.
- [ ] Lấy hết trang theo per_page=100 và lưu checkpoint sau mỗi page.
- [ ] Chuẩn hóa full_name (lowercase) khi hợp nhất repo và file hits.
- [ ] Loại trùng và dedupe theo full_name; loại fork chỉ sau khi xác minh repo.fork flag.
- [ ] Với từng repo sample, verify existence bằng /contents hoặc /git/trees thay vì tin tưởng code search duy nhất.
- [ ] Ghi lại lý do accept/reject cho mỗi repo trong trường fail_reasons.
Test-sampling tactics (kiểm thử ngẫu nhiên để phát hiện lỗi)
- Lấy sample ngẫu nhiên 50 repo từ candidate set sau hợp nhất; cho mỗi repo chạy nội kiểm: GET /repos/:owner/:repo/contents/requirements.txt và /Dockerfile. Đo tỷ lệ false negatives và false positives.
- Nếu false-negative cao, kiểm tra index lag: re-run code search cho sample sau 5-10 phút; nếu tăng thì thêm delay hoặc queue re-check cho repo vừa push.
- Nếu total_count buckets có chênh lệch lớn, audit buckets: pick 10 buckets lớn nhất, compare counts between web UI và API, document buckets đã phân đoạn.
- Log mọi query, total_count, pages fetched và thời điểm chạy để có traceability khi audit nguyên nhân bỏ sót hoặc rác.
Ví dụ minh họa chạy đầy đủ với truy vấn đã nêu và diễn giải kết quả
Mục tiêu kịch bản: lấy các repo public có README chứa một trong các cụm "machine learning", "deep learning", hoặc "model serving", ngôn ngữ chính Python, stars >= 50, không archived, và phải có cả requirements.txt và Dockerfile. Kết quả xuất ra CSV với cờ pass/reject và lý do.
- Truy vấn repository search (prototype, REST API)
- Truy vấn con người đọc được:
in:readme ( "machine learning" OR "deep learning" OR "model serving" ) language:Python stars:>50 archived:false pushed:>2021-01-01
- URL-encoded cho REST call:
q=in%3Areadme+%28%22machine%20learning%22+OR+%22deep%20learning%22+OR+%22model%20serving%22%29+language%3APython+stars%3A%3E50+archived%3Afalse+pushed%3A%3E2021-01-01
- Endpoint mẫu:
GET https://api.github.com/search/repositories?q=<URL-ENCODED-Q>&per_page=100&page=1
- Phát hiện total_count và phân mảnh nếu cần
- Kiểm tra field totalcount ở response đầu. Nếu totalcount > 900 (ngưỡng an toàn gần limit), phân mảnh theo buckets stars để tránh cap 1000:
- stars:>1000
- stars:500..1000
- stars:100..499
- stars:50..99
- Chạy từng bucket tuần tự, ghi log bucket và tổng count bucket để traceability.
- Lấy candidate_repos
- Gộp tất cả pages từ mỗi bucket, chuẩn hóa full_name thành lowercase và dedupe.
- Lưu candidate_repos set.
- Code search per-filename
- Không cố nhồi nhiều filename vào một query. Thay vào đó chạy hai queries code search riêng:
- filename:requirements.txt
- filename:Dockerfile
- REST endpoints:
GET https://api.github.com/search/code?q=filename%3Arequirements.txt&perpage=100&page=N GET https://api.github.com/search/code?q=filename%3ADockerfile&perpage=100&page=N
- Từ mỗi hit, extract item.repository.full_name và chuẩn hóa.
- Hợp nhất theo repo
- Tập hasrequirements = set of repo.fullname from first code search
- Tập has_dockerfile = set from second
- finalcandidates = candidaterepos ∩ hasrequirements ∩ hasdockerfile
- Git tree checks cho top N
- Sắp xếp finalcandidates theo stargazerscount desc, chọn top N (ví dụ N = 200) để kiểm tra chi tiết bằng git tree.
- Đối với mỗi repo trong top N:
- Lấy default branch via repository.default_branch
- GET /repos/{owner}/{repo}/git/trees/{default_branch}?recursive=1
- Kiểm tra tồn tại paths chính xác: requirements.txt, Dockerfile, và các biến thể phổ biến (requirements-dev.txt, docker/Dockerfile). Nếu không thấy trên default branch, thử branches phổ biến: main, master, develop (giới hạn tối đa 3 thêm).
- Nếu file tồn tại, GET /repos/{owner}/{repo}/contents/{path} để lấy content và xác thực token (ví dụ requirements có dấu "==" hoặc Dockerfile có FROM).
- Compute rubric cho mỗi repo
- Mandatory checks (must):
- archived == false
- has_requirements confirmed by tree/content
- has_dockerfile confirmed by tree/content
- Scoring items:
- stars: >=500 -> 3, 100..499 -> 2, 50..99 -> 1
- pushed_at: <6 months -> 2, 6-12 months ->1, else 0
- language: Python bytes >=20% OR presence of setup.py/pyproject.toml -> +1
- Decision:
- pass_overall = true if all mandatory pass and score >= 3
- else if mandatory pass but score 1-2 -> mark manual_review
- else reject with reasons aggregated
- Export CSV
- Các cột bắt buộc: fullname | htmlurl | stars | size | pushedat | language | hasrequirements | hasdockerfile | fork | archived | score | passoverall | fail_reasons
- Mẫu dòng CSV (ví dụ):
full_name,html_url,stars,size,pushed_at,language,has_requirements,has_dockerfile,fork,archived,score,pass_overall,fail_reasons
orgA/model-serving-pkg,https://github.com/orgA/model-serving-pkg,124,1500,2024-03-12,Python,true,true,false,false,5,true,
userB/old-ml-tool,https://github.com/userB/old-ml-tool,12,800,2019-10-01,Python,true,false,false,false,1,false,"missing_dockerfile;old_push"
- Ví dụ pseudo-code Python (khung, cần bổ sung token handling, pagination, backoff)
# pseudo-code, not full implementation
def search_repos(q):
r = requests.get("https://api.github.com/search/repositories", params={"q":q,"per_page":100})
return r.json()
def search_code(filename):
q = f"filename:{filename}"
# paginate and collect repository.full_name from items
return set_of_full_names
repos = set()
for bucket_q in repo_bucket_queries:
resp = search_repos(bucket_q)
if resp['total_count'] == 0: continue
repos.update(extract_full_names(resp))
# run code searches
req_repos = search_code("requirements.txt")
docker_repos = search_code("Dockerfile")
final = repos & req_repos & docker_repos
# further checks: git trees for top N, compute rubric, output CSV
- Diễn giải kết quả và hướng tinh chỉnh
- Nếu final set quá rộng:
- Tăng stars threshold, thêm pushed:> date gần hơn, hoặc require language:Python trong repo search thay vì hậu kiểm.
- Phân mảnh lại buckets nhỏ hơn để tránh bị cap 1000 và hiểu rõ phân bố.
- Nếu final set quá hẹp:
- Mở rộng biến thể filename (requirements-dev.txt, Pipfile, pyproject.toml, Dockerfile.dev), hoặc mở rộng in:readme sang in:description OR topics.
- Giảm stars threshold hoặc bỏ pushed filter tạm thời để kiểm tra recall.
- Để debug false negatives:
- Lấy sample repo từ repository search hits nhưng không vào final; chạy tree check thủ công để xem file nằm trên branch non-default hoặc tên biến thể. Nếu nhiều trường hợp như vậy, bổ sung biến thể tên hoặc thêm branch checks.
- Để kiểm soát rate limit:
- Cache repo metadata, chỉ gọi git tree cho top N hoặc những repo pass mandatory checks. Sử dụng exponential backoff và checkpoint per-page.
Kết quả cuối cùng là CSV có đủ cột để đồng đội đọc và hiểu lý do accept/reject cho từng repo, cùng logs truy vấn và bucket trace để audit bất kỳ trường hợp bỏ sót nào.
Tự động hóa thực tế: mẫu pseudo-code / cấu trúc script REST API (phases, modules, checkpoints)
Mục tiêu kỹ thuật: cung cấp kiến trúc script để tự động hóa workflow search repositories và search code qua GitHub REST API, có cấu trúc module rõ ràng (searchrepo, searchcode, pagination, merge, postprocess, csv_export), checkpointing per-page/bucket để resume an toàn, quản lý token, xử lý song song an toàn và rate-limit handling.
Kiến trúc cao cấp
- Phases (luồng logic)
- Phase A: repo discovery — chạy một hoặc nhiều truy vấn repository search, với phân mảnh (buckets) nếu total_count vượt threshold.
- Phase B: code discovery — cho mỗi filename criterion chạy code search riêng, lấy toàn bộ pages, thu set(repofullname).
- Phase C: merge — hợp nhất candidate repo theo full_name; áp logic giao/hoặc tùy yêu cầu.
- Phase D: postprocess — fetch metadata repo, loại fork/mirror theo policy, kiểm README/languages, apply rubric scoring.
- Phase E: export — ghi CSV/JSON và ghi checkpoint final.
- Modules (đóng gói theo trách nhiệm)
- searchrepo: build repo queries, call /search/repositories, detect totalcount, decide buckets.
- pagination: generic paginator cho REST calls, checkpoint per page and per bucket.
- searchcode: run code queries per filename, extract repository.fullname, checkpoint per page.
- merge: normalize full_name, dedupe, intersect/union per rules.
- postprocess: repo object fetch, languages API, readme/content verification, fork/mirror checks, rubric scoring.
- csvexport: write CSV/JSON rows, include failreasons list and provenance (which query, which bucket).
- token_manager: manage token pool, rotate tokens, read rate-limit headers, compute sleep-windows.
- utils/logging: structured logs with query->pages->counts mapping for audit.
Checkpointing and resume
- Checkpoint granularity: per-bucket per-page for repo searches; per-filename per-page for code searches; per-repo postprocess state (not_processed, processing, done, failed).
- Persist checkpoint as small JSON file with timestamps and last success page indices. On restart, resume from first incomplete checkpoint.
- Example keys:
{ "repobucket:stars1000+": {"page":3,"done":false}, "code_query:requirements.txt":{"page":10,"done":true}, "post:owner/repo":{"state":"done"} }
Token management, parallelism safe và rate-limit handling
- Token pool: accept list of personal access tokens. Each worker requests a token from token_manager which tracks X-RateLimit-Remaining and X-RateLimit-Reset for that token.
- Token selection policy: prefer token with largest remaining, or round-robin but check remaining > min_threshold before use.
- Concurrency control: use bounded worker pool (threads or async tasks) with semaphore limiting simultaneous search requests to search-specific max (search endpoints often stricter).
- Rate-limit handling: after each request, read response headers:
- X-RateLimit-Remaining, X-RateLimit-Reset, Retry-After (if present).
- If remaining <= safety_margin: mark token as sleeping until reset, remove from rotation.
- On 403/429: exponential backoff with jitter, and requeue the page for later attempt.
- Backoff strategy: base_sleep = 1s, backoff *= 2 on retries, add random jitter up to 20%.
Pseudo-code kiểu Python (khung, để triển khai)
# pseudo-code (Python-style)
from queue import Queue
import time, json, requests, threading
# modules: token_manager, pagination, search_repo, search_code, merge, postprocess, csv_export
class TokenManager:
def __init__(self, tokens):
self.tokens = [{"tok":t,"remaining":99999,"reset":0} for t in tokens]
self.lock = threading.Lock()
def acquire(self):
with self.lock:
# choose token with remaining > threshold
for entry in sorted(self.tokens, key=lambda e:-e['remaining']):
if entry['remaining'] > 10 and time.time() < entry['reset']:
return entry['tok']
# fallback: wait shortest reset
wait = min(e['reset'] - time.time() for e in self.tokens if e['reset']>time.time())
time.sleep(max(wait,1))
return self.acquire()
def update_from_headers(self, token, headers):
with self.lock:
for e in self.tokens:
if e['tok']==token:
e['remaining'] = int(headers.get('X-RateLimit-Remaining', e['remaining']))
e['reset'] = int(headers.get('X-RateLimit-Reset', e['reset']))
break
def paginated_get(url, params, token_manager, checkpoint_key, checkpoint_store):
page = checkpoint_store.get(checkpoint_key, {}).get("page", 1)
while True:
token = token_manager.acquire()
params.update({"per_page":100, "page":page})
resp = requests.get(url, params=params, headers={"Authorization":f"token {token}"})
token_manager.update_from_headers(token, resp.headers)
if resp.status_code == 200:
data = resp.json()
yield data
checkpoint_store[checkpoint_key] = {"page": page, "done": False}
# persist checkpoint_store to disk here
if not has_next_page(resp): break
page += 1
elif resp.status_code in (403,429):
# rate limited; backoff and retry
retry_after = int(resp.headers.get("Retry-After", 0))
time.sleep(max(retry_after, 5))
else:
raise RuntimeError(f"HTTP {resp.status_code} for {url}")
# example search_repo usage
def run_repo_bucket(query, bucket_name, token_manager, checkpoint_store, out_set):
url = "https://api.github.com/search/repositories"
for page_data in paginated_get(url, {"q":query}, token_manager, f"repo:{bucket_name}", checkpoint_store):
for item in page_data.get("items",[]):
out_set.add(item["full_name"].lower())
Thao tác song song an toàn
- Thiết kế: producer-consumer
- Producer: spawn tasks để runrepobucket for each bucket sequentially or low-concurrency to avoid hitting search endpoint cap.
- Consumer: code_search workers that run filename queries in parallel but bounded.
- Không parallelize phân trang một truy vấn đơn lẻ; thay vào đó parallelize nhiều queries/buckets.
Resume và auditability
- Sau mỗi page thành công, persist checkpoint file.
- Ghi logs: query, bucket, page, items_count, timestamp.
- Khi pipeline hoàn thành, export final CSV cùng checkpoint snapshot used so bạn biết queries và pages đã chạy.
CSV output schema (bắt buộc fields)
- fullname | htmlurl | stars | size | pushedat | language | hasrequirements | hasdockerfile | fork | archived | score | pass | failreasons | provenance_queries
- provenance_queries chứa list các truy vấn + bucket names that led to inclusion (giúp audit).
Gợi ý vận hành
- Run small smoke test on web UI queries to ensure query syntax right, then run REST pipeline on a small bucket first.
- Start with conservative concurrency and monitor rate-limit headers; tune token pool size and concurrency.
- Keep checkpoint file in durable storage and rotate tokens periodically.
Phần này cung cấp khung module và kỹ thuật vận hành để bạn xây script thực thi được, có checkpointing per-page/bucket, quản lý token an toàn, và xử lý parallelism với backoff.
Thước đo hiệu năng, logging và traceability để audit lỗi hợp nhất
Mục tiêu: đảm bảo mọi bước trong pipeline có thể truy lại, tái hiện và phân tích root cause khi hợp nhất giữa repository search và code search bị sai. Mình đưa ra tập metrics cần thu, cấu trúc log tối thiểu và quy ước lưu query+khoảng thời gian để audit.
Metrics cơ bản cần thu (per run, per query, per bucket)
- queriesrun: id truy vấn duy nhất, query string (URL-encoded khi gọi API), querytype (repo|code), bucket_name nếu phân mảnh.
- totalcountperquery: giá trị totalcount trả về từ API ở page 1.
- pages_fetched: số pages đã lấy, per query.
- items_returned: tổng số items thu được (sau dedupe trên query).
- reposcandidatecount: số repo duy nhất sau gộp từ repository search.
- reposwithfile_hits: số repo có ít nhất một file hit từ code search per filename.
- reposafterintersection: số repo ở bước giao giữa repo candidates và các file-sets.
- falsenegativesamplingmetric: samplesize, sampleids, manualcheckoutcomes (foundmissingfile:boolean), fnrate = foundmissing/ samplesize.
- ratelimitheaders: X-RateLimit-Remaining, X-RateLimit-Reset, per request where applicable.
- duration_ms: thời gian xử lý từng page / từng query.
- apierrors: list (statuscode, endpoint, page, retry_count).
- dedupe_actions: count duplicates removed and dedupe keys used.
Logging format và nội dung tối thiểu
- Mỗi bản ghi log phải có timestamp ISO8601, run_id, component, level, message, and structured payload.
- Ví dụ một log line JSON (ghi vào file dòng-delimited):
{"ts":"2026-07-20T10:12:03Z","run_id":"run-20260720-003","component":"search_repo","query_id":"q1-stars-500+","query":"in:readme (\"machine learning\" OR \"deep learning\") language:Python stars:>500","total_count":1240,"pages_fetched":10,"items_returned":1000,"note":"total_count>1000 triggered segmentation"}
- Lưu thêm per-request headers snapshot:
{"ts":"2026-07-20T10:12:03Z","run_id":"run-...","component":"http","endpoint":"/search/repositories","page":1,"status":200,"headers":{"X-RateLimit-Remaining":"4800","X-RateLimit-Reset":"162..."}}
Traceability để audit lỗi hợp nhất
- Giữ mapping queryid -> bucketname -> pages -> itemids -> repofull_name. Mỗi repo record kèm provenance list: which queries contributed it and timestamp.
- Khi hợp nhất theo fullname, lưu join record: repofullname, sourcesets = ["reposearch:q1","codesearch:requirements.txt:qA"], mergedtime, mergedstatus (passed|rejected|review), fail_reasons.
- Lưu snapshots of sets at major checkpoints: candidatesbeforecodesearch, candidatesaftereachfilename, finalcandidates. Snapshots dạng newline-delimited fullname lists with header metadata.
False-negative sampling workflow (audit thực tiễn)
- Sau một run, randomly sample N repos from repocandidates that did not appear in code hits. For each, perform authoritative check using Contents API or git/trees on default branch and up to K common branches. Log result into falsenegativemetric payload. Nếu fnrate > threshold, flag run for recheck and increase wait time for index-lag re-query.
Storage, retention và truy vấn logs
- Store structured logs và snapshots trong object store (S3) partitioned by run_id and date. Keep index file per run listing queries and buckets.
- Retention: keep full audit for at least 90 days; keep aggregated metrics for longer.
- Expose a small audit CSV per run: runid, queryid, totalcount, pagesfetched, reposcandidatecount, reposwithfilehits, fnrate, note.
Practical notes ngắn
- Luôn lưu nguyên văn query string cùng timestamp và token_id (ẩn token value) để biết token gây rate limit.
- Khi phát hiện total_count > threshold, log truncation reason and buckets created.
- Mỗi quyết định loại bỏ repo phải kèm fail_reasons array và provenance để truy vết.
Kịch bản vận hành theo ngân sách API và chính sách retry
Mô tả ngắn: đặt ba chế độ chạy tùy mục tiêu và ngân sách API, ước tính chi phí gọi API theo từng bước, chính sách backoff và resume để an toàn khi bị rate limit hoặc gián đoạn.
- Các chế độ chạy
- Fast sampling (low calls)
- Mục tiêu: test nhanh, shortlist nhỏ.
- Hành động: 1 repository-search phân đoạn nhẹ; code-search chỉ cho một filename phổ biến; hậu kiểm bằng GET /repos/:owner/:repo/contents cho top-N repo.
- Tính cost ước tính (per candidate batch): 1 repo-search call per query page + ~Ncontentcalls (N small). Dùng khi cần kết quả trong vài phút.
- Full harvest (high calls, multi-token)
- Mục tiêu: lấy toàn bộ tập dữ liệu cho phân tích toàn diện.
- Hành động: phân mảnh queries để tránh cap 1000 result; chạy code-search cho mọi filename biến thể; phân trang tới hết; cho mỗi candidate gọi repository metadata, languages, readme, git trees as needed.
- Tính cost ước tính: số queriesrepo × pages + sum(filequeries × pages) + per-repo metadata calls. Chuẩn bị multi-token hoặc tăng quota, chạy theo batch.
- Incremental nightly run
- Mục tiêu: cập nhật danh sách, bù index-lag.
- Hành động: chạy các truy vấn đã lưu, chỉ lấy các repo changed since lastrun (pushed:>lastrun), re-check code-search cho recent pushes, revalidate flagged repos.
- Tính cost ước tính: thấp hơn full harvest nhiều vì chỉ delta.
- Cách ước tính chi phí gọi (thực tế)
- Break down per logical action: searchrepopage, searchcodepage, repometadata, contentscheck, gittree. Tính tổng = Σ(actionscount × costperaction). Tránh hardcode global numbers; thay bằng công thức dựa trên số queries, pages, và candidate repo.
- Ví dụ mẫu: nếu bạn có 10 repo-search queries × 5 pages = 50 searchrepopage calls; 2 filename queries each with 20 pages = 40 searchcodepage calls; kết hợp 500 candidate repos × 3 metadata/contents calls = 1500 calls. Tổng = 1590 calls.
- Backoff strategy và retry policy
- Mỗi request: on 403/429 read X-RateLimit-Remaining and X-RateLimit-Reset. Nếu remaining == 0 sleep until reset. Nếu 5xx or transient network error, apply exponential backoff with full jitter: basedelay = 1s, multiply by 2 each retry, jitter uniform(0, delay). Giới hạn retrycount (ví dụ 5) để tránh vòng lặp vô tận.
- Throttling chủ động: giới hạn concurrency so với token quota. Nếu dùng nhiều token, shard queries theo token id theo round-robin, track per-token remaining.
- Idempotency và checkpoint: sau mỗi page hoặc per-repo verification persist checkpoint (queryid, page, lastrepo_processed). Khi retry resume từ checkpoint; tránh duplicate processing bằng idempotent writes.
- Resume policy và robustness
- Lưu state durable: run_id, bucket list, pages completed, candidate lists, provenance. Nếu bị kill, resume từ last saved page/bucket.
- Re-check window for index lag: nếu repo pushed_at within last T minutes and code-search returned no file, schedule re-check after delay window (e.g., 10 minutes) instead of immediate reject.
- Alerting: nếu many queries hit total_count>threshold or error rate spikes, escalate and pause full harvest.
Những nguyên tắc vận hành ngắn gọn: thiết kế theo buckets nhỏ, track provenance, dùng checkpoint để resume, ưu tiên delta runs cho nightly, và áp retry with exponential backoff plus jitter kết hợp multi-token sharding khi cần mở rộng.
Hướng dẫn thử nghiệm mẫu và đo recall/precision trên tập mẫu
Mục tiêu: chọn mẫu đại diện, tạo ground truth bằng cách đọc git trees, chạy pipeline, so sánh kết quả để tính recall và precision, tune thresholds, và thiết lập báo cáo định kỳ.
- Chọn sample
- Chọn stratified sample theo buckets: stars (high/med/low), size (small/medium/large), pushed recency (recent/old), archived (yes/no).
- Kích thước mẫu khởi điểm: 300–500 repo chia đều theo buckets. Với ngân sách API thấp, chọn 100–200 repo nhưng vẫn giữ stratification.
- Tạo ground truth
- Với mỗi repo trong mẫu, lấy git tree của default branch bằng API: GET /repos/{owner}/{repo}/git/trees/{branch}?recursive=1.
- Xác định presence của files cần kiểm thử, ví dụ requirements.txt và Dockerfile, bằng cách tìm path chính xác hoặc các biến thể. Lưu kết quả boolean và note đường dẫn nếu có. Đây là ground truth. Ghi timestamp kiểm tra.
- Chạy pipeline under test
- Chạy chính xác pipeline tự động của bạn, với cùng ngày chạy. Export output list repo được pipeline gán là pass cho tiêu chí file. Lưu method và phiên bản pipeline config.
- So sánh và tính metric
- Precision = TP / (TP + FP) where TP = repo pipeline đánh là có file và ground truth cũng có; FP = pipeline đánh có nhưng ground truth không.
- Recall = TP / (TP + FN) where FN = pipeline không phát hiện nhưng ground truth có.
- Tính F1 = 2 precision recall / (precision + recall). Báo cả counts TP, FP, FN, TN.
- Phân tích lỗi và tune thresholds
- Phân loại FN theo nguyên nhân: filename variant, branch non-default, index lag, pagination miss, fork confusion.
- Với mỗi nguyên nhân, áp biện pháp: thêm biến thể filename queries, thêm re-check branches phổ biến, tăng phân mảnh query để tránh cap 1000, hoặc thêm Contents API kiểm tra per-repo.
- Sau chỉnh, lặp lại test trên cùng mẫu. Ghi delta metric.
- Báo cáo định kỳ và triage
- Lập báo cáo weekly gồm: queries chạy, total candidates, TP/FP/FN/TN, precision/recall/F1, top 5 lỗi phổ biến, hành động tiếp theo.
- Nếu pipeline chạy trên toàn bộ corpus, chạy incremental validation mỗi tuần trên mẫu khác 10% so với mẫu trước để tránh overfitting.
- Kiểm soát thực thi
- Ghi provenance: query strings, pages fetched, timestamps, token id. Giữ log để audit khi thấy bất thường.
- Nếu phát hiện total_count>threshold trong bất kỳ truy vấn nào, đánh dấu bucket đó và kiểm tra phân mảnh lại trước khi chốt kết quả.
Kết quả cần giao: file CSV mẫu chứa fullname | groundtruthhasfiles | pipelinehasfiles | TP/FP/FN flag, cùng summary metrics và notes cho từng lỗi chính.
Danh sách lý do loại bỏ chuẩn để dùng trong cột fail_reasons và cách tự sinh lý do
Mục tiêu: cung cấp tập mã lý do chuẩn để gán tự động khi repo bị loại, cùng quy tắc engine để map các kiểm tra thành lý do rõ ràng, dễ đọc và parse được.
Danh sách mã lý do chuẩn (tên ngắn, dùng trong cột fail_reasons)
- archived
- fork
- mirror
- missing_requirements
- missing_dockerfile
- language_mismatch
- starsbelowthreshold
- sizetoolarge
- inactive
- no_readme
- indexlagsuspected
- rate_limited
- pagination_truncated
- missing_license
- private_repo
- failscontentvalidation
Quy ước sinh lý do
- Mỗi repo có thể có nhiều lý do; lưu dưới dạng chuỗi phân tách dấu chấm phẩy. Ví dụ: "missingrequirements;languagemismatch".
- Luôn kèm metadata ngắn gọn nếu cần debug, theo mẫu key=value trong ngoặc vuông. Ví dụ: missingrequirements[checked=2024-07-01], starsbelow_threshold[value=12,threshold=50].
- Lý do bắt buộc trước tiên: những lý do kill-switch (archived, private_repo, mirror) được kiểm trước; nếu khớp và policy yêu cầu reject ngay thì dừng kiểm tra tiếp.
Rules engine – xử lý thứ tự và cách map
- Bước 1: thu metadata repo (repo API): archived, private, fork, mirrorurl, stargazerscount, size, pushed_at, language.
- Nếu archived true -> add "archived" and stop if policy auto-reject archived.
- Nếu private true -> add "private_repo" and stop.
- Nếu mirror_url present -> add "mirror" and stop or flag per policy.
- Bước 2: siêu dữ liệu đánh giá: stars, size, pushed_at, language
- Nếu stars < threshold -> add "starsbelowthreshold[…]" (but continue; may still accept via other rules).
- Nếu size > threshold -> add "sizetoolarge[…]".
- If primary language absent or low share -> add "language_mismatch[…]".
- If pushed_at older than inactivity window -> add "inactive[…]".
- Bước 3: file-presence checks (code search or contents API)
- Nếu requirements variants none found -> add "missing_requirements".
- Nếu Dockerfile variants none found -> add "missing_dockerfile".
- If code search returned results but fewer than expected and pushedat very recent -> add "indexlag_suspected".
- If contents fetched but file content fails lightweight validation (tokens) -> add "failscontentvalidation".
- Bước 4: operational checks
- Nếu a search query hit totalcount cap or API returned truncated results -> add "paginationtruncated".
- If rate limit encountered during repo processing -> add "rate_limited".
Mẫu logic mapping (ví dụ Python pseudo-code)
def evaluate_repo(repo_meta, file_checks, config):
reasons = []
if repo_meta['archived']:
reasons.append('archived')
return reasons
if repo_meta['private']:
reasons.append('private_repo'); return reasons
if repo_meta.get('mirror_url'):
reasons.append('mirror'); return reasons
if repo_meta['stargazers_count'] < config['min_stars']:
reasons.append(f"stars_below_threshold[value={repo_meta['stargazers_count']},thr={config['min_stars']}]")
if repo_meta['size'] > config['max_size_kb']:
reasons.append(f"size_too_large[value={repo_meta['size']}]")
if not file_checks['requirements']:
reasons.append('missing_requirements')
if not file_checks['dockerfile']:
reasons.append('missing_dockerfile')
if file_checks.get('index_lag'):
reasons.append('index_lag_suspected')
return reasons
Ghi chú vận hành ngắn
- Gán lý do ngay khi phát hiện để audit traceability. Lưu cả query và timestamp kiểm tra.
- Giữ thứ tự kiểm tra nhất quán để dễ đọc logs: kill-switch metadata -> heuristic metadata -> file-presence -> operational.
Checklist deployment và vận hành (runbook)
Mục tiêu: đảm bảo lần chạy production an toàn, có thể tái lập và dễ audit.
Trước khi chạy production
- Xác thực token và secrets
- Quay vòng token truy cập API; áp policy rotation định kỳ (ví dụ 30-90 ngày).
- Kiểm tra quyền token tối thiểu cần thiết; lưu token trong secret manager.
- CVE và secret scanning
- Chạy quét secret trên script và config (truffleHog, detect-secrets hoặc dịch vụ tương đương).
- Áp kiểm tra CVE cho bất kỳ dependency nào dùng để chạy pipeline.
- Block deploy nếu phát hiện secret rò rỉ hoặc CVE nghiêm trọng.
- Chuẩn bị dữ liệu đầu ra
- Định nghĩa schema CSV/JSON đầu ra; tạo thư mục backup versioned.
- Tự động xuất backup CSV trước khi overwrite; giữ ít nhất 3 phiên bản gần nhất.
- Giới hạn và cảnh báo
- Thiết lập alert nếu bất kỳ truy vấn search trả total_count >1000; log query, timestamp, đề xuất phân mảnh.
- Alert khi thấy rate limit, 403/429 hoặc timeout trong phân trang.
- Kế hoạch cho index lag và re-run
- Nếu repo vừa pushed trong window gần (configurable, ví dụ 10 phút) và code search không trả kết quả, đánh dấu để re-run sau delay (ví dụ 5–15 phút) tối đa 3 lần; log lý do.
- Đánh dấu soft-includes cho trường hợp nghi ngờ index lag, đẩy vào review queue.
Vận hành và hồi phục
- Checkpointing
- Ghi checkpoint sau mỗi page/bucket đã fetch để resume khi lỗi.
- Logging và audit
- Lưu query, total_count, pages fetched, buckets phân mảnh, và token id dùng.
- Khi có sự cố
- Tạm dừng pipeline, snapshot current CSV, thông báo team, điều tra logs, thực hiện re-run theo checkpoint.
- Quyền truy cập khẩn cấp
- Danh sách người có thể rotate token và access logs; thực hiện hành động theo playbook đã kí.
Các bước tiếp theo tùy chọn: dùng gh CLI, parallel tokens, hoặc chuyển sang GitHub Enterprise
Mình trình bày khi nào lựa chọn mỗi công cụ hoặc mô hình là hợp lý, những trade-off chính, và gợi ý cách tích hợp vào pipeline đã có để schedule harvest qua CI/Terraform. Đầu tiên xác định mục tiêu vận hành: bạn cần thu thập một lần lớn, chạy định kỳ với độ phủ cao, hay làm harvest incremental với latency thấp. Từ đó chọn đường đi.
- Khi gh CLI hữu ích
- Khi bạn muốn prototype nhanh, chạy kiểm tra trên máy dev hoặc máy CI nhỏ, và dùng flow tương tác. gh support cả search và api wrappers, dễ tích hợp script shell.
- Ưu điểm: không cần code nhiều, dễ debug, output JSON có thể pipe vào jq để xử lý.
- Hạn chế: gh CLI vẫn dùng token đơn lẻ, không phù hợp khi cần throughput cao; khó kiểm soát pagination và parallelism ở quy mô lớn.
- Khi cần multi-token (parallel tokens)
- Khi lượng repo/code search lớn và bạn bị rate limit. Phân phối workload qua nhiều tokens giảm thời gian wall-clock. Dùng token pool với round-robin request hoặc per-bucket token assignment (ví dụ mỗi stars-bucket dùng token khác).
- Ưu điểm: tăng throughput, rút ngắn thời gian harvest.
- Rủi ro: cần quản lý token rotation an toàn, coord retries, và tránh vi phạm ToS. Cần central rate-limiter để tránh spike trên một token. Log token id (ẩn) để audit.
- Khi chuyển sang GitHub Enterprise / GHES
- Khi bạn có volume cực lớn, cần SLA, hoặc muốn truy cập API enterprise với higher rate limits và private data. GHES/Enterprise Cloud có mức quota khác và thường cho phép enterprise token management.
- Ưu điểm: rate limits cao hơn, infrastructure controllable, network proximity cho faster cloning.
- Hạn chế: chi phí, cần admin setup, có thể không phù hợp nếu mục tiêu là public GitHub rộng lớn.
- Trade-offs tóm tắt
- Simplicity (gh CLI) vs Throughput (multi-token) vs Scale+SLA (Enterprise).
- Chi phí vận hành lên theo thứ tự trên. Độ phức tạp orchestration, secrets management và audit tăng khi dùng multi-token hoặc Enterprise.
- Tích hợp CI/Terraform để schedule harvest
- CI: đặt pipeline job theo cron (GitHub Actions, GitLab CI, Jenkins). Job stages: (a) run repo-search buckets, (b) run per-filename code-search or per-repo contents checks, (c) postprocess + export CSV. Sử dụng job matrix để parallelize buckets; assign each matrix runner a token from secret store.
- Terraform: quản lý infra cho runners, secrets (vault/Secrets Manager), scheduler infra (Cloud Scheduler) và role bindings cho GitHub App/token. Terraform cũng provision state for checkpoints storage (S3/GCS) để resume.
- Patterns: rate-limiter sidecar hoặc centralized request queue; token vault rotation via CI secrets; idempotent jobs that write incremental checkpoints after each page/bucket.
- Operational cautions
- Log every query and total_count to detect >1000 cap and force fragmentation.
- Limit parallelism per token; implement exponential backoff on 403/429.
- Keep small pilot runs after config changes to validate behavior before full harvest.
Cuối cùng, chọn giải pháp phù hợp với ngân sách và mục tiêu recall/latency. Nếu bạn cần, mình có thể đưa cấu trúc job matrix mẫu cho GitHub Actions + pseudo-workflow phân phối token và checkpointing.
Kết luận
Sau khi triển khai pipeline theo hướng dẫn, bạn sẽ có một luồng nhiều pha đáng tin cậy để tìm, lọc và gán nhãn các repository phù hợp rồi xuất kết quả máy đọc được (CSV/JSON) kèm cờ và lý do. Mình lưu ý thực tế nhất: hãy thiết kế sampling và checkpointing để phát hiện độ trễ chỉ mục và giới hạn trả về của API, nhằm cho phép resume an toàn và tránh mất mát hoặc bỏ sót khi chạy ở quy mô.