channel-worker 2.5.102 → 2.5.103
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/shopee-scraper.js +37 -7
- package/package.json +1 -1
package/lib/shopee-scraper.js
CHANGED
|
@@ -173,11 +173,39 @@ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
|
173
173
|
// or logged out — in either case the offer API returns 404/empty and we must
|
|
174
174
|
// STOP (not keep hammering) and ask the operator to re-verify via VNC.
|
|
175
175
|
function isBlockedState(pageUrl, res) {
|
|
176
|
-
if (/\/verify\/captcha|\/buyer\/login|is_from_login/.test(pageUrl || '')) return true;
|
|
177
176
|
if (res && (res.status === 403 || res.status === 404)) return true;
|
|
177
|
+
let u;
|
|
178
|
+
try { u = new URL(pageUrl || ''); } catch { return false; }
|
|
179
|
+
// CHỈ xét path + query của CHÍNH trang này. Regex quét cả chuỗi URL đọc nhầm
|
|
180
|
+
// tham số `next=` của trang khác — xem ghi chú ở findAffiliatePage.
|
|
181
|
+
if (/^\/(verify\/captcha|buyer\/login)/.test(u.pathname)) return true;
|
|
182
|
+
if (u.searchParams.has('is_from_login')) return true;
|
|
178
183
|
return false;
|
|
179
184
|
}
|
|
180
185
|
|
|
186
|
+
// Tab affiliate nào đang mở — so bằng HOSTNAME, không bằng chuỗi con.
|
|
187
|
+
//
|
|
188
|
+
// 11/09/2026, đo trên profile Shopee1 thật: tab captcha cũ
|
|
189
|
+
// `shopee.vn/verify/captcha?…&next=…%252Faffiliate.shopee.vn%252Foffer…`
|
|
190
|
+
// (mã hoá hai lần) chứa nguyên chuỗi "affiliate.shopee.vn", nên
|
|
191
|
+
// `pages().find(p => p.url().includes('affiliate.shopee.vn'))` vớ trúng ĐÚNG
|
|
192
|
+
// cái tab hỏng đó — nó nằm ở vị trí 0 trong 16 tab. isBlockedState đọc
|
|
193
|
+
// `/verify/captcha` rồi kết luận "session ở captcha", trong khi 15 tab còn lại
|
|
194
|
+
// đang đăng nhập bình thường (dashboard, conversion_report, offer). Chủ tịch mở
|
|
195
|
+
// profile thấy vẫn login mà máy một mực đòi xác minh — hai bên nhìn hai tab
|
|
196
|
+
// khác nhau.
|
|
197
|
+
//
|
|
198
|
+
// Bỏ QUA tab đang ở trạng thái chặn khi chọn: phiên chết thật thì không tab nào
|
|
199
|
+
// sống sót, hàm trả null và bên gọi mở tab mới — lượt goto sau đó vẫn bị đá về
|
|
200
|
+
// captcha nên vẫn báo needs_verify đúng như cũ.
|
|
201
|
+
function urlPath(u) { try { return new URL(u).pathname; } catch { return ''; } }
|
|
202
|
+
function isAffiliateUrl(u) { try { return new URL(u).hostname === 'affiliate.shopee.vn'; } catch { return false; } }
|
|
203
|
+
|
|
204
|
+
function findAffiliatePage(pages) {
|
|
205
|
+
const live = (pages || []).filter((p) => isAffiliateUrl(p.url()) && !isBlockedState(p.url(), null));
|
|
206
|
+
return live.find((p) => urlPath(p.url()).startsWith('/offer')) || live[0] || null;
|
|
207
|
+
}
|
|
208
|
+
|
|
181
209
|
// Apply the Flow-1 hard filters (criteria) to parsed rows. Defaults are lenient;
|
|
182
210
|
// the controller passes the agreed thresholds (commission/price/sold/rating/imgs).
|
|
183
211
|
function applyFilters(rows, f = {}) {
|
|
@@ -237,11 +265,11 @@ async function scrapeOffers(ctx, {
|
|
|
237
265
|
const listType = cat?.list_type || null;
|
|
238
266
|
|
|
239
267
|
const pagesOpen = ctx.pages();
|
|
240
|
-
let page = pagesOpen
|
|
268
|
+
let page = findAffiliatePage(pagesOpen);
|
|
241
269
|
let opened = false;
|
|
242
270
|
if (!page) { page = await ctx.newPage(); opened = true; }
|
|
243
271
|
try {
|
|
244
|
-
if (!page.url().
|
|
272
|
+
if (!urlPath(page.url()).startsWith('/offer')) {
|
|
245
273
|
// 'networkidle' (playwright), KHÔNG 'networkidle2' (tên của puppeteer —
|
|
246
274
|
// playwright coi là giá trị lạ và ném lỗi).
|
|
247
275
|
await page.goto('https://affiliate.shopee.vn/offer/product_offer', { waitUntil: 'networkidle', timeout: timeoutMs }).catch(() => {});
|
|
@@ -326,11 +354,11 @@ async function generateOfferLink(ctx, { items = [], subIds = [], timeoutMs = 450
|
|
|
326
354
|
for (let i = 0; i < 5; i++) subs[`subId${i + 1}`] = cleanSubId(subIds[i]);
|
|
327
355
|
|
|
328
356
|
const pagesOpen = ctx.pages();
|
|
329
|
-
let page = pagesOpen
|
|
357
|
+
let page = findAffiliatePage(pagesOpen);
|
|
330
358
|
let opened = false;
|
|
331
359
|
if (!page) { page = await ctx.newPage(); opened = true; }
|
|
332
360
|
try {
|
|
333
|
-
if (!page.url()
|
|
361
|
+
if (!isAffiliateUrl(page.url())) {
|
|
334
362
|
await page.goto('https://affiliate.shopee.vn/offer/product_offer', { waitUntil: 'networkidle', timeout: timeoutMs }).catch(() => {});
|
|
335
363
|
await sleep(1500);
|
|
336
364
|
}
|
|
@@ -376,11 +404,13 @@ async function generateOfferLink(ctx, { items = [], subIds = [], timeoutMs = 450
|
|
|
376
404
|
// Parse a Shopee product URL → { shop_id, item_id }. Accepts
|
|
377
405
|
// /product/<shop>/<item> and i.<shop>.<item> forms.
|
|
378
406
|
function parseProductUrl(url = '') {
|
|
379
|
-
|
|
407
|
+
// `/opaanlp/` là đích của link affiliate (đo 12/09: link rút gọn s.shopee.vn
|
|
408
|
+
// đá về đúng dạng này) — vẫn là một sản phẩm, vẫn lấy được shop/item.
|
|
409
|
+
let m = url.match(/\/(?:product|opaanlp)\/(\d+)\/(\d+)/);
|
|
380
410
|
if (m) return { shop_id: m[1], item_id: m[2] };
|
|
381
411
|
m = url.match(/i\.(\d+)\.(\d+)/);
|
|
382
412
|
if (m) return { shop_id: m[1], item_id: m[2] };
|
|
383
413
|
return null;
|
|
384
414
|
}
|
|
385
415
|
|
|
386
|
-
module.exports = { ingestProduct, scrapeOffers, generateOfferLink, cleanSubId, parseProductUrl, parseOfferRow, parsePdpItem, categoryGroup, pctToNumber, AFFILIATE_CATEGORIES, applyFilters };
|
|
416
|
+
module.exports = { ingestProduct, scrapeOffers, generateOfferLink, cleanSubId, parseProductUrl, parseOfferRow, parsePdpItem, categoryGroup, pctToNumber, AFFILIATE_CATEGORIES, applyFilters, isBlockedState, findAffiliatePage };
|