Use qty dropdown for availability count, not just scarcity text
The 'We have X left' scarcity indicator only appears when availability is low (typically ≤5). The quantity <select> dropdown in the booking form always shows 0..N where N = actual available qty (capped at 10). Extract max option value from the <select> in the last table cell per rate plan row, falling back to regex parsing of the cell text if the select isn't rendered, then to the scarcity text as a last resort. Stored in both rooms_left and available_qty on RateData. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
5462773dd7
commit
f2e11bf585
1 changed files with 28 additions and 3 deletions
|
|
@ -73,15 +73,39 @@ _EXTRACT_RATES_JS = """
|
||||||
const cancelMatch = condCell.match(/free cancellation before ([\\w\\s]+)/i);
|
const cancelMatch = condCell.match(/free cancellation before ([\\w\\s]+)/i);
|
||||||
const freeCancelText = cancelMatch ? cancelMatch[1].trim() : null;
|
const freeCancelText = cancelMatch ? cancelMatch[1].trim() : null;
|
||||||
|
|
||||||
// Persons: first <td> ("Max persons: 2" or "Only for 1 guest")
|
// Persons: first <td> ("Max persons: 2" or "Only for 1 guest")
|
||||||
const personsCell = cells.length >= 1 ? cells[0].innerText || '' : '';
|
const personsCell = cells.length >= 1 ? cells[0].innerText || '' : '';
|
||||||
const personsMatch = personsCell.match(/\\d+/);
|
const personsMatch = personsCell.match(/\\d+/);
|
||||||
const maxPersons = personsMatch ? parseInt(personsMatch[0]) : null;
|
const maxPersons = personsMatch ? parseInt(personsMatch[0]) : null;
|
||||||
|
|
||||||
|
// Quantity dropdown: last <td> has a <select> with options 0..N
|
||||||
|
// where N = actual available qty (capped at 10 by Booking.com).
|
||||||
|
// This is more reliable than the "X left" scarcity text which only
|
||||||
|
// appears when availability is low (typically ≤5).
|
||||||
|
let qtyAvailable = availCount; // fallback to scarcity text
|
||||||
|
const qtyCell = cells.length >= 4 ? cells[cells.length - 1] : null;
|
||||||
|
if (qtyCell) {
|
||||||
|
const sel = qtyCell.querySelector('select');
|
||||||
|
if (sel) {
|
||||||
|
const vals = Array.from(sel.options)
|
||||||
|
.map(o => parseInt(o.value))
|
||||||
|
.filter(v => !isNaN(v) && v > 0);
|
||||||
|
if (vals.length > 0) qtyAvailable = Math.max(...vals);
|
||||||
|
} else {
|
||||||
|
// Fallback: parse max number from cell text
|
||||||
|
// "Select rooms\\n0\\n1 (£208)\\n2 (£416)" → [1,2] → max 2
|
||||||
|
const nums = (qtyCell.innerText || '').match(/^(\\d+)/gm);
|
||||||
|
if (nums && nums.length > 0) {
|
||||||
|
const parsed = nums.map(n => parseInt(n)).filter(v => v > 0);
|
||||||
|
if (parsed.length > 0) qtyAvailable = Math.max(...parsed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
results.push({
|
results.push({
|
||||||
room_id: roomId,
|
room_id: roomId,
|
||||||
room_name: roomName,
|
room_name: roomName,
|
||||||
avail_count: availCount,
|
avail_count: qtyAvailable,
|
||||||
block_id: blockId,
|
block_id: blockId,
|
||||||
price: priceRaw ? parseInt(priceRaw) : null,
|
price: priceRaw ? parseInt(priceRaw) : null,
|
||||||
breakfast_included: breakfastIncluded,
|
breakfast_included: breakfastIncluded,
|
||||||
|
|
@ -282,7 +306,8 @@ class PlaywrightHotelPageBackend(ScraperBackend):
|
||||||
room_type=plan['room_name'] or None,
|
room_type=plan['room_name'] or None,
|
||||||
breakfast_included=plan['breakfast_included'],
|
breakfast_included=plan['breakfast_included'],
|
||||||
free_cancellation=not plan['non_refundable'],
|
free_cancellation=not plan['non_refundable'],
|
||||||
rooms_left=plan['avail_count'],
|
rooms_left=plan['avail_count'], # qty from dropdown (max 10) or scarcity text
|
||||||
|
available_qty=plan['avail_count'],
|
||||||
rate_plan_id=plan['block_id'] or None,
|
rate_plan_id=plan['block_id'] or None,
|
||||||
max_persons=plan['max_persons'],
|
max_persons=plan['max_persons'],
|
||||||
))
|
))
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue