Skip to content

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

URL Discovery and Content Scrape

Pipeline untuk mencari URL adengan memanfaatkan teknik google dorking, menyimpan hasil ke JSON, dan mengambil isi konten halaman dengan Playwright.

Entry point utama sekarang adalah:

python scripts/get_url.py

Script ini menjalankan pencarian Google, menyimpan URL yang lolos filter, lalu secara acak dapat mengambil isi konten dari URL pending di pipeline yang sama.

Struktur

url-discovery/
├── .env.example
├── config.py
├── research_config.example.json
├── requirements.txt
├── scripts/
│   ├── get_url.py
│   ├── get_content.py
│   ├── build_dataset.py
│   └── setup_browser_profile.py
├── services/
│   ├── browser_guard.py
│   ├── content_service.py
│   ├── dataset_service.py
│   ├── json_service.py
│   ├── query_service.py
│   ├── search_service.py
│   └── url_service.py
└── data/
    ├── blacklist.json
    ├── contents.json
    ├── datasets/
    │   ├── dataset_<timestamp>.json
    │   └── index.json
    ├── search_state.json
    └── urls.json

Instalasi

pip install -r requirements.txt
python -m playwright install chromium

Setup Profil Browser

Jalankan setup profil sebelum scraping:

python scripts/setup_browser_profile.py

Chromium akan terbuka dengan profil persistent di data/browser_profile. Login akun Google, selesaikan consent/captcha jika muncul, lalu tekan Enter di terminal. Profil ini dipakai ulang oleh get_url.py dan get_content.py.

Pipeline Utama

python scripts/get_url.py

Alur saat ini:

  1. Membuat kombinasi query pendek dari LOCATIONS, TOPIC_GROUPS, ISSUE_GROUPS, PRIORITY_DORKS, SOCIAL_DOMAINS, dan KNOWN_ENTITIES.
  2. Query yang sudah done di data/search_state.json dilewati, kecuali RESEARCH_COMPLETED_QUERIES=True.
  3. Jika RANDOMIZE_QUERY_ORDER=True, urutan query diacak.
  4. Chromium dibuka dengan persistent profile.
  5. Tab Chromium pertama langsung dipakai untuk query pertama, tanpa membuka tab blank tambahan.
  6. Hasil Google dinormalisasi, difilter, dideduplikasi, lalu disimpan ke data/urls.json.
  7. URL yang lolos filter juga dapat dikirim ke background extractor HTTP strict berbasis httpx.AsyncClient + BeautifulSoup.
  8. Dari hasil yang sedang tampil di halaman Google, pipeline bisa memilih URL yang lolos filter secara acak untuk ekstraksi konten Playwright.
  9. Konten Playwright dibuka dengan klik anchor hasil Google ke tab baru, bukan dengan membuka canonical URL langsung dari file JSON.
  10. Saat mengambil konten, tab konten dibiarkan standby seperti sedang membaca, melakukan scroll kecil acak, lalu ditutup.
  11. Setelah tab konten ditutup, fokus kembali ke tab Google dork.
  12. Jika masih ada halaman hasil berikutnya, pagination/query berikutnya dijalankan dari tab Google tersebut.
  13. Jika captcha terdeteksi, proses berhenti dan alasan captcha disimpan di state/output terkait.

Jika data/datasets/index.json sudah ada dan SKIP_BUILT_DATASET_ITEMS=true, get_url.py melewati query yang sudah dibuild serta URL canonical yang sudah pernah masuk dataset.

Konfigurasi tidak lagi ditulis langsung sebagai konstanta besar di config.py.

  • .env berisi konstanta runtime: tanggal, delay, limit, timeout, path data, dan toggle pipeline.
  • research_config.json berisi data riset: lokasi, topik, isu, domain prioritas, entitas, blacklist domain, tracking params, dan keyword filter.
  • config.py hanya membaca .env dan JSON, lalu mengekspos nama konstanta yang dipakai service lain.

File aktif .env dan research_config.json masuk .gitignore. Template yang bisa dilacak repo adalah .env.example dan research_config.example.json.

Konfigurasi runtime utama di .env:

RESEARCH_CONFIG_FILE="research_config.json"
DATA_DIR="data"
DATASETS_DIR="data/datasets"
DATASET_INDEX_FILE="data/datasets/index.json"
SKIP_BUILT_DATASET_ITEMS=true
START_DATE="2024-01-01"
END_DATE="2026-06-26"
MAX_PAGES_PER_QUERY=1
MAX_QUERIES_PER_RUN=0
RANDOMIZE_QUERY_ORDER=true
SHORT_QUERY_MODE=true
MAX_GOOGLE_QUERY_LENGTH=180
SEARCH_QUERY_DELAY_MIN_SECONDS=18
SEARCH_QUERY_DELAY_MAX_SECONDS=60
ROTATE_USER_AGENTS=false
RELOAD_FAILED_QUERIES=false
HTTP_CONTENT_ENABLED=true
HTTP_CONTENT_MAX_WORKERS=2
HTTP_CONTENT_TIMEOUT_SECONDS=20
HTTP_CONTENT_MAX_BYTES=750000
HTTP_CONTENT_MIN_TEXT_LENGTH=300
HTTP_CONTENT_REQUIRE_TITLE=true
HTTP_CONTENT_REQUIRE_QUERY_OVERLAP=true
HTTP_CONTENT_MIN_QUERY_TERM_OVERLAP=2
HTTP_CONTENT_REQUIRE_RESULT_TITLE_OVERLAP=true
HTTP_CONTENT_MIN_RESULT_TITLE_OVERLAP=1

Data riset utama di research_config.json:

{
  "search_label": "SHS/PLTS Kalimantan Barat",
  "primary_location": "Kalimantan Barat",
  "stable_topic_terms": ["PLTS", "panel surya", "tenaga surya"],
  "locations": ["Kalimantan Barat", "Kalbar", "Bengkayang"],
  "topic_groups": {
    "household_solar": ["Solar Home System", "panel surya rumah"],
    "village_solar": ["PLTS desa", "PLTS komunal"]
  },
  "issue_groups": {
    "experience": ["warga", "pengalaman", "keluhan warga"],
    "benefit": ["manfaat", "penghematan"],
    "problem": ["rusak", "tidak berfungsi"]
  },
  "priority_dorks": ["site:kalbar.antaranews.com", "site:kompas.com"],
  "social_domains": ["youtube.com/watch", "facebook.com"],
  "known_entities": ["Pulau Lemukutan", "Desa Temajuk"],
  "excluded_domains": ["shopee.co.id", "tokopedia.com"],
  "tracking_params": ["utm_source", "utm_medium", "fbclid"],
  "url_required_keywords": ["panel surya", "plts", "energi terbarukan"]
}

Jika research_config.json belum ada, config.py otomatis memakai research_config.example.json.

SHORT_QUERY_MODE=True membuat query Google dipecah menjadi banyak kombinasi pendek. Query tidak lagi memakai OR (...) panjang untuk semua sinonim sekaligus. MAX_GOOGLE_QUERY_LENGTH menjadi batas aman agar query yang terlalu panjang tidak dikirim.

Contoh bentuk query pendek:

"panel surya rumah" "Kalimantan Barat" after:2024-01-01 before:2026-06-27
site:kalbar.antaranews.com "PLTS desa" "Kalimantan Barat" after:2024-01-01 before:2026-06-27
"PLTS" "keluhan" "Bengkayang" after:2024-01-01 before:2026-06-27

MAX_QUERIES_PER_RUN=5 berarti satu run hanya mengambil maksimal 5 query pending. Jika ingin menjalankan semua query pending dalam satu run, set nilainya ke 0.

Random Konten Di Pipeline Utama

Pengambilan konten acak di get_url.py dikontrol oleh:

PIPELINE_CONTENT_ENABLED = True
PIPELINE_CONTENT_CHANCE = 0.95
PIPELINE_CONTENT_CONTINUE_CHANCE = 0.80
PIPELINE_MAX_CONTENT_BURST = 2
PIPELINE_CONTENT_DELAY_MIN_SECONDS = 8
PIPELINE_CONTENT_DELAY_MAX_SECONDS = 20

Nilai PIPELINE_CONTENT_CHANCE dan PIPELINE_CONTENT_CONTINUE_CHANCE memakai skala 0.0 sampai < 1.0, sesuai random.random(). Contoh: 0.95 berarti sekitar 95%.

Kandidat konten hanya berasal dari hasil yang sedang terlihat di halaman Google dork dan:

  • tidak masuk blacklist;
  • content_status belum success;
  • content_status belum pdf_skipped.

Karena konten diambil lewat klik hasil Google, URL lama yang ada di data/urls.json tetapi tidak tampil pada halaman Google saat ini tidak dipilih oleh pipeline utama. Untuk memproses backlog dari file, gunakan python scripts/get_content.py.

Background HTTP Content Strict

Selain ekstraksi Playwright acak, get_url.py dapat menjalankan ekstraksi latar belakang untuk setiap URL yang sudah lolos UrlService.merge_result(). Mode ini memakai httpx.AsyncClient untuk mengambil HTML dan BeautifulSoup untuk parsing. Worker berjalan di background, tetapi hasilnya tetap digabung dan disimpan dari main thread sehingga penulisan data/urls.json, data/contents.json, dan data/search_state.json tetap tersentral.

Mode ini bersifat oportunistik dan konservatif. Hasil HTTP hanya masuk ke data/contents.json jika semua guard strict lolos:

  • HTTP status 2xx;
  • content-type HTML;
  • redirect tetap ke host yang sama setelah normalisasi www;
  • title wajib ada dan cukup panjang;
  • halaman tidak terlihat seperti captcha, block page, login challenge, atau JavaScript/cookie wall;
  • teks cukup panjang;
  • isi memuat salah satu URL_REQUIRED_KEYWORDS;
  • isi memiliki overlap minimum dengan query Google;
  • isi memiliki overlap minimum dengan title hasil Google.

Jika guard menolak halaman, content_status tidak diubah menjadi failed. URL tetap bisa diproses oleh Playwright, baik dari pipeline utama maupun python scripts/get_content.py.

Hasil Playwright tetap diprioritaskan. Jika contents.json sudah memiliki status="success", hasil HTTP tidak menimpa record tersebut. Jika Playwright kemudian berhasil untuk URL yang sama, record Playwright akan menggantikan record HTTP.

Konfigurasi mode HTTP strict:

HTTP_CONTENT_ENABLED=true
HTTP_CONTENT_MAX_WORKERS=2
HTTP_CONTENT_TIMEOUT_SECONDS=20
HTTP_CONTENT_MAX_BYTES=750000
HTTP_CONTENT_MIN_TEXT_LENGTH=300
HTTP_CONTENT_REQUIRE_TITLE=true
HTTP_CONTENT_MIN_TITLE_LENGTH=8
HTTP_CONTENT_REQUIRE_QUERY_OVERLAP=true
HTTP_CONTENT_MIN_QUERY_TERM_OVERLAP=2
HTTP_CONTENT_REQUIRE_RESULT_TITLE_OVERLAP=true
HTTP_CONTENT_MIN_RESULT_TITLE_OVERLAP=1
HTTP_CONTENT_USER_AGENT=""

Jika dependency httpx atau beautifulsoup4 belum tersedia, get_url.py akan menonaktifkan mode HTTP background dan tetap menjalankan pipeline Playwright normal.

Filter URL

URL yang ditemukan tidak langsung disimpan semuanya. UrlService hanya menerima hasil yang:

  • bisa dinormalisasi menjadi URL HTTP/HTTPS;
  • bukan domain di EXCLUDED_DOMAINS;
  • mengandung salah satu URL_REQUIRED_KEYWORDS;
  • belum menjadi duplikat canonical URL.

Pengecekan URL_REQUIRED_KEYWORDS tidak case sensitive dan dilakukan terhadap title, snippet, URL asli, serta canonical URL.

Captcha Handling

services/browser_guard.py mendeteksi captcha dari:

  • URL marker seperti /sorry/, captcha, atau recaptcha;
  • elemen captcha/recaptcha/hcaptcha;
  • title halaman;
  • teks seperti unusual traffic atau verifikasi robot.

Stop otomatis hanya berlaku jika captcha muncul pada halaman Google setelah query dijalankan. Captcha, challenge, atau halaman verifikasi di situs konten/non-Google tidak menghentikan pipeline.

Jika captcha Google Search muncul:

  • proses pencarian langsung berhenti;
  • query dianggap gagal;
  • alasan disimpan sebagai captcha_reason;
  • URL halaman captcha disimpan sebagai captcha_url;
  • title halaman disimpan sebagai captcha_title;
  • jalankan ulang setup profil:
python scripts/setup_browser_profile.py

Output

data/urls.json

Berisi URL unik yang lolos filter:

  • url
  • canonical_url
  • domain
  • title
  • snippet
  • published_date_hint
  • is_blacklisted
  • first_found_at
  • last_found_at
  • found_by
  • content_status
  • content_checked_at
  • content_length
  • content_error
  • captcha_reason
  • captcha_url
  • captcha_title
  • http_content_checked_at
  • http_content_method
  • http_content_status
  • http_content_reject_reason

Field konten/captcha hanya muncul ketika sudah relevan.

data/search_state.json

Menyimpan status per query:

  • pending
  • running
  • done
  • failed

State juga menyimpan jumlah halaman selesai, jumlah hasil, jumlah URL unik baru, hasil yang terfilter, error, dan alasan captcha jika ada.

Bagian meta.checkpoint menyimpan posisi terakhir:

  • current_query_id
  • current_query
  • current_page
  • last_query_id
  • last_query
  • last_status
  • last_page
  • current_content_url
  • last_content_url
  • last_content_status

Jika proses berhenti di tengah query, run berikutnya melanjutkan dari pages_done + 1 untuk query berstatus running.

data/contents.json

Berisi hasil ekstraksi konten:

  • canonical_url
  • source_url
  • extracted_at
  • status
  • http_status
  • content_type
  • final_url
  • title
  • description
  • published_at
  • text
  • extraction_method
  • guard
  • error
  • captcha_reason
  • captcha_url
  • captcha_title

Status konten dapat berupa pending, running, success, too_short, pdf_skipped, atau failed.

data/content_state.json

Dibuat oleh python scripts/get_content.py untuk checkpoint ekstraksi konten terpisah. File ini menyimpan:

  • total_candidates
  • completed_urls
  • failed_urls
  • checkpoint.current_url
  • checkpoint.current_index
  • checkpoint.last_url
  • checkpoint.last_status

get_content.py mengambil URL dengan content_status="pending" atau content_status="running". Status running diproses ulang agar proses yang mati paksa bisa dilanjutkan. URL yang sudah success, too_short, pdf_skipped, atau failed tidak diproses ulang otomatis. Untuk mengulang URL tertentu, ubah statusnya kembali ke pending.

data/failure_analysis.json

Dibuat otomatis saat terjadi kegagalan di get_url.py atau get_content.py. File ini append-only dan berisi event analitik untuk investigasi:

  • failed_at
  • process (get_url atau get_content)
  • stage (search_error, search_captcha, pipeline_content_error, content_error, dan sejenisnya)
  • error_type
  • error
  • context.query_run_index
  • context.query
  • context.page
  • context.url
  • context.burst_index
  • context.content_extract_count_before
  • context.content_delay_seconds
  • checkpoint
  • totals.unique_urls
  • totals.contents
  • totals.query_status_counts
  • totals.content_status_counts

data/blacklist.json

Berisi URL yang tidak perlu diproses sebagai konten baru. Jika muncul lagi dari hasil pencarian, URL tetap dapat dicatat tetapi diberi is_blacklisted: true.

data/datasets/dataset_<timestamp>.json

Dibuat oleh:

python scripts/build_dataset.py

File ini adalah output siap konsumsi. Bagian records menggabungkan metadata URL dari data/urls.json, provenance found_by, dan hasil konten dari data/contents.json pada field content. Bagian queries menyimpan query dari data/search_state.json yang statusnya sudah selesai diproses dan bukan failed.

data/datasets/index.json

Index dataset historis yang dipakai run berikutnya untuk mencegah data lama dipakai ulang:

  • canonical_urls
  • query_ids
  • queries
  • datasets

Jika SKIP_BUILT_DATASET_ITEMS=true, get_url.py melewati query/URL dari index ini dan get_content.py melewati URL yang sudah masuk dataset.

Ekstraksi Konten Terpisah

Pipeline utama mengambil konten lewat klik hasil yang sedang tampil di Google. Jika ingin menjalankan ekstraksi konten dari URL yang sudah ada di file:

python scripts/get_content.py

Script ini membaca data/urls.json, memproses URL yang belum sukses atau belum pdf_skipped, lalu menyimpan hasil ke data/contents.json. Jika dataset index sudah ada, URL yang sudah masuk dataset dilewati.

Build Dataset Dan Reset Runtime

Setelah URL dan konten terkumpul, build dataset konsumsi akhir:

python scripts/build_dataset.py

Script ini akan:

  1. membaca data/urls.json, data/contents.json, dan data/search_state.json;
  2. membuat data/datasets/dataset_<timestamp>.json;
  3. memperbarui data/datasets/index.json;
  4. mereset data/search_state.json, data/urls.json, dan data/contents.json.

Jika ingin membuat dataset tanpa reset runtime:

python scripts/build_dataset.py --no-reset

failure_analysis.json dan content_state.json tidak direset oleh script ini.

Mengulang Pencarian

Secara default query berstatus done tidak diulang.

Untuk mengulang query yang sudah selesai:

RESEARCH_COMPLETED_QUERIES=true

Untuk mengulang query yang gagal:

RETRY_FAILED_QUERIES=true

Untuk memprioritaskan query yang gagal terlebih dahulu sebelum lanjut ke alur normal:

RELOAD_FAILED_QUERIES=true

Saat aktif, query failed dijalankan dulu. Setelah query gagal habis, script lanjut ke query normal berdasarkan state terakhir. Kembalikan RELOAD_FAILED_QUERIES=false setelah selesai jika tidak ingin failed selalu diprioritaskan.

Bagian Yang Paling Sering Diubah

Ubah data riset di research_config.json:

  • locations
  • topic_groups
  • issue_groups
  • priority_dorks
  • social_domains
  • known_entities
  • url_required_keywords
  • excluded_domains

Ubah konstanta runtime di .env:

  • SHORT_QUERY_MODE
  • MAX_GOOGLE_QUERY_LENGTH
  • PIPELINE_CONTENT_*
  • SEARCH_QUERY_DELAY_*

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages