Repository navigation
Expand file tree
/
Copy pathscrape.py
More file actions
137 lines (115 loc) · 5.25 KB
/
Copy pathscrape.py
File metadata and controls
137 lines (115 loc) · 5.25 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
"""Booking.com scraper in Python: hotel prices, ratings and availability to CSV.
Runs the Booking.com Hotels Scraper (an Apify Actor) for one or more
destinations and saves one row per property to a CSV file.
export APIFY_TOKEN="<your-apify-token>"
python scrape.py "Lisbon, Portugal" --check-in 2026-11-20 --check-out 2026-11-23
python scrape.py "Paris, France" "Rome, Italy" --hotels-only --sort price --max-results 20
"""
import argparse
import csv
import json
import os
import sys
from decimal import Decimal
from apify_client import ApifyClient
ACTOR = "myth84/booking-hotels-scraper"
# Columns written to the CSV. Every row of the dataset has more (see the README).
COLUMNS = [
"platformListingId",
"title",
"propertyType",
"city",
"region",
"checkIn",
"checkOut",
"nights",
"currency",
"pricePerNight",
"priceTotal",
"priceBeforeDiscount",
"taxesAndFees",
"rating",
"reviewCount",
"starRating",
"cancellationPolicy",
"badges",
"latitude",
"longitude",
"searchLocation",
"url",
]
def make_parser(description):
parser = argparse.ArgumentParser(description=description)
parser.add_argument("locations", nargs="+", help='destinations as typed on Booking.com, e.g. "Lisbon, Portugal"')
parser.add_argument("--check-in", help="check-in date, YYYY-MM-DD (with --check-out)")
parser.add_argument("--check-out", help="check-out date, YYYY-MM-DD")
parser.add_argument("--check-in-days", type=int, default=30,
help="without fixed dates: check-in in N days, recomputed at every run (default 30)")
parser.add_argument("--nights", type=int, default=3, help="without fixed dates: nights of the stay (default 3)")
parser.add_argument("--adults", type=int, default=2)
parser.add_argument("--currency", default="EUR", help="ISO code: EUR, USD, GBP...")
parser.add_argument("--sort", choices=["popularity", "price"], default="popularity",
help="Booking.com's top picks or lowest price first")
parser.add_argument("--hotels-only", action="store_true", help="hotels only, no apartments or hostels")
parser.add_argument("--min-score", type=int, choices=[6, 7, 8, 9], help="minimum review score out of 10")
parser.add_argument("--max-results", type=int, default=50,
help="properties per destination (default 50); you pay per property saved")
parser.add_argument("--max-charge", type=Decimal, help="stop the run once it has cost this many USD")
return parser
def build_input(args):
if bool(args.check_in) != bool(args.check_out):
sys.exit("--check-in and --check-out go together.")
run_input = {
"locations": args.locations,
"adults": args.adults,
"currency": args.currency,
"sortBy": args.sort,
"maxResults": args.max_results,
}
if args.check_in:
run_input["checkIn"] = args.check_in
run_input["checkOut"] = args.check_out
else:
run_input["checkInDaysFromNow"] = args.check_in_days
run_input["nights"] = args.nights
if args.hotels_only:
run_input["propertyTypes"] = ["hotels"]
if args.min_score:
run_input["minReviewScore"] = args.min_score
return run_input
def run_actor(run_input, max_charge=None):
"""Runs the Actor, waits for it to finish and returns the rows of its dataset."""
if not os.environ.get("APIFY_TOKEN"):
sys.exit("Set APIFY_TOKEN first: Apify Console > Settings > API & Integrations.")
client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor(ACTOR).call(run_input=run_input, max_total_charge_usd=max_charge)
if run is None:
sys.exit("The run could not be started.")
status = getattr(run.status, "value", run.status) # SUCCEEDED, FAILED, TIMED-OUT...
if status != "SUCCEEDED":
sys.exit(f"The run ended as {status}: open its log in Apify Console.")
return list(client.dataset(run.default_dataset_id).iterate_items())
def cell(value):
"""Lists and objects as JSON text, so that every CSV row has the same columns."""
return json.dumps(value, ensure_ascii=False) if isinstance(value, (list, dict)) else value
def write_csv(rows, path, columns=COLUMNS, append=False):
new_file = not (append and os.path.exists(path))
with open(path, "a" if append else "w", newline="", encoding="utf-8") as f:
writer = csv.writer(f)
if new_file:
writer.writerow(columns)
for row in rows:
writer.writerow([cell(row.get(column)) for column in columns])
def main():
parser = make_parser("Scrape Booking.com hotel prices, ratings and availability to CSV.")
parser.add_argument("--output", default="booking_hotels.csv", help="CSV file (default booking_hotels.csv)")
args = parser.parse_args()
rows = run_actor(build_input(args), args.max_charge)
write_csv(rows, args.output)
print(f"{len(rows)} properties saved to {args.output}")
priced = sorted((row for row in rows if row.get("priceTotal") is not None), key=lambda row: row["priceTotal"])
for row in priced[:5]:
print(f'{row["priceTotal"]:>9} {row["currency"]} for {row["nights"]} nights '
f'{row.get("rating") or "-":>4}/10 {row["title"]}')
if __name__ == "__main__":
main()