Home

AutoScout24 API

search_example_short.py
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()
21

AutoScout24 API Wrapper

Overview

Selenium
Python

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.

search_example.py
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()
31
search_query.py
1@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
68
query_validator.py
1@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
52

Python 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.