AutoScout24 API
1from auto24_api import Auto24API
2from auto24_api.search import Filters, SearchQuery
3
4
5def main() -> None:
6 with Auto24API() as api:
7 res = api.search_listings(
8 SearchQuery(
9 make=[Filters.MAKE.AUDI, Filters.MAKE.VW],
10 year_from=2014,
11 year_to=None,
12 km_from=None,
13 km_to=60_000,
14 # ...
15 )
16 )
17 print(res)
18
19if __name__ == "__main__":
20 main()
21AutoScout24 API Wrapper
Overview
This project involved building an API wrapper for the popular car listing website, AutoScout24, using Python and various libraries such as Selenium and Beautiful Soup.
The aim was to create a user-friendly API with custom validator classes, a query encoder factory, headless support with connection retries, custom exceptions, and enums for search or details function parameters. The end product was packaged into a pip package.
Development
Python, Selenium, and Beautiful Soup
Highlights
The AutoScout24 API wrapper was built using Python, Selenium, and Beautiful Soup. These tools were used to extract data from the AutoScout24 website, validate user function parameters, and encode Python class options into HTTPS requests. The project also used headless support with connection retries and included custom exceptions to handle errors.
1from auto24_api import Auto24API
2from auto24_api.search import Filters, SearchQuery
3
4
5def main() -> None:
6 """
7 The API is used in a context manager to ensure that the session is closed.
8 The Filters class is used to define developer friendly filters for the
9 search query
10 """
11 with Auto24API() as api:
12 res = api.search_listings(
13 SearchQuery(
14 make=[Filters.MAKE.AUDI, Filters.MAKE.BMW, Filters.MAKE.VW],
15 year_from=2014,
16 year_to=None,
17 km_from=None,
18 km_to=60_000,
19 price_from=7_500,
20 price_to=20_000,
21 hp_from=180,
22 sorting=Filters.SORTING.HP_DESC,
23 page_size=60,
24 )
25 )
26 print(res)
27
28
29if __name__ == "__main__":
30 main()
311@dataclass
2class SearchQuery(AbstractQuery):
3 """
4 This class is used to define the query params that will be used in the request
5 """
6 vehicule_type: VehiculeType = VehiculeType.CAR
7 make: Union[Make, list[Make]] = Make.ALL
8 year_from: Union[None, int] = None
9 year_to: Union[None, int] = None
10 km_from: Union[None, int] = None
11 km_to: Union[None, int] = None
12 # ...
13
14 def __post_init__(self) -> None:
15 if isinstance(self.make, Make):
16 self.make = [self.make]
17
18 self._qv = QueryValidator(self)
19 valid, error = self._qv.validate()
20 if not valid:
21 raise QueryParamsValidationError(error)
22 # ...
23
24 @property
25 def VALIDATORS(self) -> list[Validator]:
26 """
27 This property is used to define the validators that will be applied to the
28 class input params
29
30 The validators are defined in the __annotations__ attribute of the class
31 and are used to validate the input params of the class
32 """
33 VALS = [
34 IsIn(key="year_from", min=1975, max=dt.datetime.now().year),
35 # Here is interesting to mention that we can use the year_from value in
36 # the min param of the year_to validator
37 IsIn(key="year_to", min="year_from", max=dt.datetime.now().year),
38 IsIn(key="km_from", min=0, max=500_000),
39 IsIn(key="km_to", min="km_from", max=500_000),
40 # ...
41 ]
42 # This assert is here to make sure that the validators are up to date
43 assert len(self.__annotations__.keys()) == 19, (
44 "Validators should be up to date with class input params "
45 f"({len(self.__annotations__.keys())})"
46 )
47 return VALS
48
49 @property
50 def KEY_MAPPING(self) -> dict[str, str]:
51 """
52 This property is used to define the mapping between the class input params
53 and the query params that will be used in the request
54 """
55 MAPPING = {
56 "vehicule_type": "vehtyp",
57 "make": "make",
58 "year_from": "yearfrom",
59 "year_to": "yearto",
60 "km_from": "kmfrom",
61 "km_to": "kmto",
62 # ...
63 }
64 assert len(self.__annotations__.keys()) == len(
65 MAPPING.keys()
66 ), "Mapping keys should be up to date with class input params"
67 return MAPPING
681@dataclass
2class IsIn(Validator):
3 """
4 This class is used to validate that the input value is in a given range
5 """
6 min: Union[int, str]
7 max: Union[int, str]
8
9 def validate(self, cls: object, value: Union[None, int]) -> bool:
10 """
11 This method validates that the input value is in a given range and
12 looks up the min and max values in the class if they are defined as
13 strings
14 """
15 if value is None:
16 return True
17
18 min_repr = ""
19 if isinstance(self.min, str):
20 min_repr = f"{self.min}="
21 self.min = getattr(cls, self.min) or 0 # <--- Lookup here
22
23 max_repr = ""
24 if isinstance(self.max, str):
25 max_repr = f"{self.max}="
26 self.max = getattr(cls, self.max) or 0 # <--- Lookup here
27
28 if isinstance(value, int):
29 if not self.min <= value <= self.max:
30 self.error = (
31 f"'{self.key}' is out of range "
32 f"({min_repr}{self.min} <= "
33 f"{self.key}={value} <= "
34 f"{max_repr}{self.max})"
35 )
36 return False
37 return True
38
39@dataclass
40class QueryValidator:
41 """
42 This class is used to validate the input params of the AbstractQuery classes
43 """
44 cls: "AbstractQuery"
45
46 def validate(self) -> tuple[bool, Union[str, None]]:
47 for v in self.cls.VALIDATORS:
48 value = getattr(self.cls, v.key)
49 if not v.validate(self.cls, value):
50 return False, v.error
51 return True, None
52Python Pip
Packaging
The AutoScout24 API wrapper was packaged into a pip package, making it easy for other developers to use the project in their own code. The package includes all of the necessary files and dependencies, and can be installed with a single command.
Conclusion
Learning Experience
Reflexion
The AutoScout24 API wrapper was packaged into a pip package, making it easy for other developers to use the project in their own code. The package includes all of the necessary files and dependencies, and can be installed with a single command.