# Changelog of Bloomberg News Searcher - Search by Keyword, Category & Region (`xtracto/bloomberg-news-search`) Actor

- **URL**: https://apify.com/xtracto/bloomberg-news-search/changelog.md
- **Full Actor documentation**: https://apify.com/xtracto/bloomberg-news-search.md

## Changelog & Anti-Bot Strategy Updates

Dokumen ini merangkum semua perubahan arsitektur dan strategi bypass yang telah diimplementasikan pada **bloomberg-news-search** untuk menghadapi sistem keamanan PerimeterX (HUMAN Security).

### 1. Implementasi "Primer Fetch" (Pemanasan IP)

**Masalah Sebelumnya:** Permintaan langsung ke Endpoint API JSON (`nemo-next/api/search/query`) dengan IP baru (atau IP tanpa *cookie* riwayat) langsung memicu blokade (HTTP 403) oleh sistem ant-bot PerimeterX. Bloomberg mengharuskan pengunjung untuk mendarat di halaman HTML terlebih dahulu.
**Solusi:**

- Menambahkan Request HTML dasar (*Primer Fetch*) ke `https://www.bloomberg.com/search` sebelum mengeksekusi request ke API.
- Dengan cara ini, server Bloomberg akan merespons dengan Header dan `Set-Cookie` dasar (seperti `bdfpc`), sehingga saat permintaan API dilakukan pada *TCP Connection Pool* yang sama, API merespons dengan kode 200 (Sukses).
- *Primer Fetch* ini dipanggil secara otomatis pada:
  1. Pengikisan Halaman 1.
  2. Kapan pun *Actor* me-*rotate* Proxy ke IP baru karena IP lama terbakar/diblokir pada halaman tertentu.

### 2. Dynamic Session Proxy Rotation (Ganti IP Paksa)

**Masalah Sebelumnya:** Saat IP Residensial Apify dipertahankan (*IP Stickiness*) untuk seluruh *script*, IP tersebut pada akhirnya akan terkena batas *rate limit* atau blokir (biasanya pada halaman ke-5 hingga ke-8). Setelah diblokir, sisa pencarian akan terus-terusan mengembalikan 403.
**Solusi:**

- Membiarkan IP konstan untuk mencoba hemat masa pakai IP.
- Menambahkan **Retry Logic** cerdas di dalam fungsi `_fetch()` di mana apabila terjadi respons HTTP 403, sistem akan:
  1. Menutup koneksi `ScraplingFetcher` yang telah dinodai.
  2. Menyuntikkan *ID Session* acak (`session-12345...`) ke dalam string `groups-RESIDENTIAL` Apify Proxy secara *on-the-fly*.
  3. Hal ini secara instan membunuh IP lama dan memaksa server Apify mencarikan IP perumahan (*Residential IP*) yang 100% baru.
  4. Lakukan kembali *Primer Fetch* pada IP baru tersebut sebelum menyentuh API.

### 3. Isolasi Environment Fetcher (`ScraplingFetcher`)

**Masalah Sebelumnya:** `httpx` (di balik Scrapling) menyimpan *cookies* dan TCP Connection dalam satu sesi abadi. Jika suatu koneksi di-cap sebagai "Bot" oleh PerimeterX, maka status tersebut membekas di `fetcher`.
**Solusi:**

- Jika *Actor* terdeteksi dan dirotasi, obyek `ScraplingFetcher` di re-instansiasi sepenuhnya (`fetcher = ScraplingFetcher()`), me-*reset* *state Browser/TLS Impersonation*, mematikan *Keep-Alive* lama, serta menghapus seluruh jejak *cookies* beracun.

### 4. Randomisasi Profil Peramban (TLS Impersonate)

- Profil *user-agent* / *fingerprint* bawaan Scrapling/`curl_cffi` diacak di tiap putaran menggunakan *array* konfigurasi `["safari2601", "safari260"]` yang secara konstan merotasi bagaimana "bentuk" browser kita di hadapan pengecekan PerimeterX.

***

#### Catatan Penting Mengenai Batasan (Bottleneck)

Seperti yang ditunjukkan oleh *script* pengujian lokal `tes.py` yang mengeksekusi *cookies* PerimeterX valid secara manual namun tetap diblokir; sistem API `nemo-next` menyortir *visitor* dengan sangat ketat (bahkan mengekspektasi verifikasi *Javascript* sensor).

Pencapaian 30 - 63 Artikel per kata kunci secara murni menggunakan Request HTTP "buta" ini adalah **kemenangan absolut** atas kelemahan celah *IP Trust* (memanfaatkan reputasi IP perumahan Apify). PerimeterX umumnya akan memutus nafas scraping ini saat mendeteksi anomali penarikan API berantai dari *session* yang tidak mengeksekusi *Javascript*, sehingga proses yang berhenti di kisaran *page* 4 - 8 adalah wajar (Hard Limit API).

### 5. Rotasi Profil TLS Saat Hard-Limit (2026-05)

**Masalah:** Dengan satu profil TLS (`safari2601`) PX akan memutus IP setelah 4–6 *page* dan retry dengan IP rotation gagal karena Apify datacenter punya pool IP yang terbatas — 403 langsung berlanjut, scraping fallback ke 10 artikel HTML.

**Penemuan:** Ketika pengujian sistematis terhadap semua impersonation `curl_cffi`, ternyata profil TLS yang berbeda mendapat *trust budget* yang berbeda dari PerimeterX di IP Apify yang **sama**. Profil yang efektif: `chrome145` > `chrome142` > `chrome146` > `edge101` > `safari2601`. Profil seperti `chrome120/131/136` dan `firefox133/144` langsung 403.

**Solusi (`scrape_search_paginated`):**

- Saat satu impersonation kena 403 (atau body 200 tapi non-JSON / `tosv.html`):
  1. Re-instansiasi `ScraplingFetcher()` (buang cookies + TCP pool yang sudah "kotor").
  2. Rotasi ke impersonation berikutnya pada `IMPERSONATION_ROTATION`.
  3. Jalankan ulang *deep-primer* (markets → search) dengan impersonation BARU agar PX cookies di-set ulang sesuai *fingerprint* baru.
  4. Coba ulang halaman yang sama, BUKAN halaman berikutnya.
- Berhenti hanya jika (a) semua impersonation habis, (b) >=3 *consecutive block* setelah rotasi (IP benar-benar terbakar), atau (c) `maxPages` tercapai / tidak ada artikel baru.

**Hasil:** Pada test lokal `keywords=["trump"], maxPages=25`:

- Sebelumnya: 4 page (≈40 artikel) lalu fallback ke 10 HTML = total 10.
- Sesudahnya: 18 page sukses, **169 artikel unik via API**.
- Untuk keyword "tesla": 20 page sukses, **197 artikel unik**.
- Bisa lebih jika `maxPages` dinaikkan.

**Konsekuensi:**

- `SAFE_PAGE_LIMIT` dinaikkan dari 5 → 50 (cap menjadi *guard* terhadap ekspektasi user, bukan limitasi PX).
- HTML fallback hanya dipakai jika **0 page API** sukses untuk keyword tersebut (bukan menambah artikel kalau API sudah jalan).
