blocket-api 0.3.91__tar.gz → 0.3.92__tar.gz
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.
- {blocket_api-0.3.91 → blocket_api-0.3.92}/PKG-INFO +12 -5
- {blocket_api-0.3.91 → blocket_api-0.3.92}/README.md +10 -4
- {blocket_api-0.3.91 → blocket_api-0.3.92}/blocket_api/__init__.py +2 -0
- blocket_api-0.3.92/blocket_api/ad_parser.py +182 -0
- {blocket_api-0.3.91 → blocket_api-0.3.92}/blocket_api/blocket.py +13 -6
- {blocket_api-0.3.91 → blocket_api-0.3.92}/blocket_api/constants.py +76 -14
- {blocket_api-0.3.91 → blocket_api-0.3.92}/blocket_api.egg-info/PKG-INFO +12 -5
- {blocket_api-0.3.91 → blocket_api-0.3.92}/blocket_api.egg-info/SOURCES.txt +1 -0
- {blocket_api-0.3.91 → blocket_api-0.3.92}/blocket_api.egg-info/requires.txt +1 -0
- {blocket_api-0.3.91 → blocket_api-0.3.92}/pyproject.toml +2 -1
- {blocket_api-0.3.91 → blocket_api-0.3.92}/LICENSE +0 -0
- {blocket_api-0.3.91 → blocket_api-0.3.92}/blocket_api.egg-info/dependency_links.txt +0 -0
- {blocket_api-0.3.91 → blocket_api-0.3.92}/blocket_api.egg-info/top_level.txt +0 -0
- {blocket_api-0.3.91 → blocket_api-0.3.92}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: blocket_api
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.92
|
|
4
4
|
Summary: A python API wrapper for blocket.se
|
|
5
5
|
Author-email: dunderrrrrr <emil.bjorkroth@gmail.com>
|
|
6
6
|
Requires-Python: >=3.10
|
|
@@ -16,6 +16,7 @@ Requires-Dist: mkdocs>=1.6.1
|
|
|
16
16
|
Requires-Dist: mkdocs-material>=9.6.21
|
|
17
17
|
Requires-Dist: mkdocs-swagger-ui-tag>=0.7.2
|
|
18
18
|
Requires-Dist: websockets>=15.0.1
|
|
19
|
+
Requires-Dist: beautifulsoup4>=4.14.2
|
|
19
20
|
Requires-Dist: ruff>=0.14.5
|
|
20
21
|
Dynamic: license-file
|
|
21
22
|
|
|
@@ -59,8 +60,8 @@ from blocket_api import (
|
|
|
59
60
|
|
|
60
61
|
api = BlocketAPI()
|
|
61
62
|
|
|
62
|
-
#
|
|
63
|
-
|
|
63
|
+
# search all of blocket
|
|
64
|
+
api.search(
|
|
64
65
|
"Vinterdäck Audi",
|
|
65
66
|
sort_order=SortOrder.PRICE_ASC,
|
|
66
67
|
locations=[Location.STOCKHOLM, Location.UPPSALA],
|
|
@@ -68,16 +69,22 @@ result = api.search(
|
|
|
68
69
|
)
|
|
69
70
|
|
|
70
71
|
# search for cars only
|
|
71
|
-
|
|
72
|
+
api.search_car(
|
|
72
73
|
"Audi", # query is optional
|
|
73
74
|
sort_order=CarSortOrder.MILEAGE_ASC,
|
|
74
75
|
models=[CarModel.AUDI],
|
|
75
76
|
colors=[CarColor.GULD],
|
|
76
77
|
price_from=10000,
|
|
77
78
|
price_to=50000,
|
|
78
|
-
|
|
79
|
+
transmissions=[CarTransmission.MANUAL],
|
|
79
80
|
locations=[Location.STOCKHOLM],
|
|
80
81
|
)
|
|
82
|
+
|
|
83
|
+
# Get a recommerce or car ad
|
|
84
|
+
from blocket_api import CarAd, RecommerceAd
|
|
85
|
+
|
|
86
|
+
api.get_ad(RecommerceAd(12345678))
|
|
87
|
+
api.get_ad(CarAd(12345678))
|
|
81
88
|
```
|
|
82
89
|
|
|
83
90
|
## 📝 Notes
|
|
@@ -38,8 +38,8 @@ from blocket_api import (
|
|
|
38
38
|
|
|
39
39
|
api = BlocketAPI()
|
|
40
40
|
|
|
41
|
-
#
|
|
42
|
-
|
|
41
|
+
# search all of blocket
|
|
42
|
+
api.search(
|
|
43
43
|
"Vinterdäck Audi",
|
|
44
44
|
sort_order=SortOrder.PRICE_ASC,
|
|
45
45
|
locations=[Location.STOCKHOLM, Location.UPPSALA],
|
|
@@ -47,16 +47,22 @@ result = api.search(
|
|
|
47
47
|
)
|
|
48
48
|
|
|
49
49
|
# search for cars only
|
|
50
|
-
|
|
50
|
+
api.search_car(
|
|
51
51
|
"Audi", # query is optional
|
|
52
52
|
sort_order=CarSortOrder.MILEAGE_ASC,
|
|
53
53
|
models=[CarModel.AUDI],
|
|
54
54
|
colors=[CarColor.GULD],
|
|
55
55
|
price_from=10000,
|
|
56
56
|
price_to=50000,
|
|
57
|
-
|
|
57
|
+
transmissions=[CarTransmission.MANUAL],
|
|
58
58
|
locations=[Location.STOCKHOLM],
|
|
59
59
|
)
|
|
60
|
+
|
|
61
|
+
# Get a recommerce or car ad
|
|
62
|
+
from blocket_api import CarAd, RecommerceAd
|
|
63
|
+
|
|
64
|
+
api.get_ad(RecommerceAd(12345678))
|
|
65
|
+
api.get_ad(CarAd(12345678))
|
|
60
66
|
```
|
|
61
67
|
|
|
62
68
|
## 📝 Notes
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import re
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from bs4 import BeautifulSoup, Tag
|
|
7
|
+
from httpx import Response
|
|
8
|
+
|
|
9
|
+
from .constants import SITE_URL
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass(frozen=True)
|
|
13
|
+
class RecommerceAd:
|
|
14
|
+
id: int
|
|
15
|
+
|
|
16
|
+
@property
|
|
17
|
+
def url(self) -> str:
|
|
18
|
+
return f"{SITE_URL}/recommerce/forsale/item/{self.id}"
|
|
19
|
+
|
|
20
|
+
def parse(self, response: Response) -> Any:
|
|
21
|
+
soup = BeautifulSoup(response.content, "html.parser")
|
|
22
|
+
json_script_tag = soup.select_one(
|
|
23
|
+
'script:-soup-contains("window.__staticRouterHydrationData")'
|
|
24
|
+
)
|
|
25
|
+
if not json_script_tag:
|
|
26
|
+
return {}
|
|
27
|
+
|
|
28
|
+
if match := re.search(r'JSON\.parse\("(.+)"\)', json_script_tag.text):
|
|
29
|
+
raw_json = match.group(1)
|
|
30
|
+
escaped = raw_json.encode("utf-8").decode("unicode_escape")
|
|
31
|
+
return json.loads(escaped.encode("latin1").decode("utf-8"))
|
|
32
|
+
else:
|
|
33
|
+
return {}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass(frozen=True)
|
|
37
|
+
class CarAd:
|
|
38
|
+
id: int
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def url(self) -> str:
|
|
42
|
+
return f"{SITE_URL}/mobility/item/{self.id}"
|
|
43
|
+
|
|
44
|
+
def parse(self, response: Response) -> dict:
|
|
45
|
+
soup = BeautifulSoup(response.content, "html.parser")
|
|
46
|
+
grid = soup.find("div", class_="grid grid-cols-1 md:grid-cols-3 md:gap-x-32")
|
|
47
|
+
|
|
48
|
+
if not grid:
|
|
49
|
+
return {}
|
|
50
|
+
|
|
51
|
+
data: dict[str, Any] = {"url": self.url}
|
|
52
|
+
|
|
53
|
+
self._extract_title_and_subtitle(grid, data)
|
|
54
|
+
self._extract_quick_specs(grid, data)
|
|
55
|
+
self._extract_price(grid, data)
|
|
56
|
+
self._extract_description(grid, data)
|
|
57
|
+
self._extract_specifications(grid, data)
|
|
58
|
+
self._extract_equipment(grid, data)
|
|
59
|
+
self._extract_seller_type(soup, data)
|
|
60
|
+
self._extract_ad_id(soup, data)
|
|
61
|
+
|
|
62
|
+
return data
|
|
63
|
+
|
|
64
|
+
def _extract_title_and_subtitle(self, grid: Tag, data: dict[str, Any]) -> None:
|
|
65
|
+
if title := grid.find("h1", class_=lambda x: x and "t1" in x):
|
|
66
|
+
data["title"] = title.get_text(strip=True)
|
|
67
|
+
|
|
68
|
+
if subtitle := grid.find(
|
|
69
|
+
"p", class_=lambda x: x and "s-text-subtle" in x and "mt-8" in x
|
|
70
|
+
):
|
|
71
|
+
data["subtitle"] = subtitle.get_text(strip=True)
|
|
72
|
+
|
|
73
|
+
def _extract_quick_specs(self, grid: Tag, data: dict[str, Any]) -> None:
|
|
74
|
+
if specs_grid := grid.find(
|
|
75
|
+
"div", class_=lambda x: x and "grid" in x and "gap-24" in x
|
|
76
|
+
):
|
|
77
|
+
spec_items = specs_grid.find_all("div", class_="flex gap-16 hyphens-auto")
|
|
78
|
+
for item in spec_items:
|
|
79
|
+
label = item.find("span", class_="s-text-subtle")
|
|
80
|
+
value = item.find("p", class_="m-0 font-bold")
|
|
81
|
+
if label and value:
|
|
82
|
+
label_text = label.get_text(strip=True)
|
|
83
|
+
value_text = value.get_text(strip=True)
|
|
84
|
+
|
|
85
|
+
key_mapping = {
|
|
86
|
+
"Modellår": "model_year",
|
|
87
|
+
"Miltal": "mileage",
|
|
88
|
+
"Växellåda": "transmission",
|
|
89
|
+
"Drivmedel": "fuel",
|
|
90
|
+
}
|
|
91
|
+
key = key_mapping.get(
|
|
92
|
+
label_text, label_text.lower().replace(" ", "_")
|
|
93
|
+
)
|
|
94
|
+
data[key] = value_text
|
|
95
|
+
|
|
96
|
+
def _extract_price(self, grid: Tag, data: dict[str, Any]) -> None:
|
|
97
|
+
if price_section := grid.find("div", class_="border-t pt-40 mt-40"):
|
|
98
|
+
price_labels = price_section.find_all("p", class_="s-text-subtle mb-0")
|
|
99
|
+
for price_label in price_labels:
|
|
100
|
+
label_text = price_label.get_text(strip=True).lower()
|
|
101
|
+
if "pris" in label_text:
|
|
102
|
+
price_elem = price_section.find("span", class_="t2")
|
|
103
|
+
if price_elem:
|
|
104
|
+
data["price"] = price_elem.get_text(strip=True)
|
|
105
|
+
break
|
|
106
|
+
elif "månadskostnad" in label_text:
|
|
107
|
+
monthly_elem = price_section.find("h2", class_="t2")
|
|
108
|
+
if monthly_elem:
|
|
109
|
+
data["monthly_cost"] = monthly_elem.get_text(strip=True)
|
|
110
|
+
break
|
|
111
|
+
|
|
112
|
+
def _extract_description(self, grid: Tag, data: dict[str, Any]) -> None:
|
|
113
|
+
desc_sections = grid.find_all("section", class_="border-t mt-40 pt-40")
|
|
114
|
+
for section in desc_sections:
|
|
115
|
+
h2 = section.find("h2", class_="t3 mb-0")
|
|
116
|
+
if h2 and "beskrivning" in h2.get_text(strip=True).lower():
|
|
117
|
+
desc_div = section.find("div", class_="whitespace-pre-wrap")
|
|
118
|
+
if desc_div:
|
|
119
|
+
data["description"] = desc_div.get_text(strip=True)
|
|
120
|
+
break
|
|
121
|
+
|
|
122
|
+
def _extract_specifications(self, grid: Tag, data: dict[str, Any]) -> None:
|
|
123
|
+
specs_section = grid.find("section", class_="key-info-section")
|
|
124
|
+
if not specs_section:
|
|
125
|
+
return
|
|
126
|
+
|
|
127
|
+
dl = specs_section.find("dl")
|
|
128
|
+
if not dl:
|
|
129
|
+
return
|
|
130
|
+
|
|
131
|
+
specifications: dict[str, str] = {}
|
|
132
|
+
divs = dl.find_all("div", style="break-inside:avoid-column")
|
|
133
|
+
for div in divs:
|
|
134
|
+
dt = div.find("dt")
|
|
135
|
+
dd = div.find("dd")
|
|
136
|
+
if dt and dd:
|
|
137
|
+
key_text = dt.get_text(strip=True)
|
|
138
|
+
value_text = dd.get_text(strip=True)
|
|
139
|
+
specifications[key_text] = value_text
|
|
140
|
+
|
|
141
|
+
if specifications:
|
|
142
|
+
data["specifications"] = specifications
|
|
143
|
+
|
|
144
|
+
def _extract_equipment(self, grid: Tag, data: dict[str, Any]) -> None:
|
|
145
|
+
equipment_section: Tag | None = None
|
|
146
|
+
for section in grid.find_all("section", class_="border-t pt-40 mt-40"):
|
|
147
|
+
h2 = section.find("h2", class_="t3 mb-0")
|
|
148
|
+
if h2 and "utrustning" in h2.get_text(strip=True).lower():
|
|
149
|
+
equipment_section = section
|
|
150
|
+
break
|
|
151
|
+
|
|
152
|
+
if not equipment_section:
|
|
153
|
+
return
|
|
154
|
+
|
|
155
|
+
equipment_list = equipment_section.find("ul")
|
|
156
|
+
if equipment_list:
|
|
157
|
+
equipment_items: list[str] = [
|
|
158
|
+
li.get_text(strip=True) for li in equipment_list.find_all("li")
|
|
159
|
+
]
|
|
160
|
+
if equipment_items:
|
|
161
|
+
data["equipment"] = equipment_items
|
|
162
|
+
|
|
163
|
+
def _extract_seller_type(self, soup: BeautifulSoup, data: dict[str, Any]) -> None:
|
|
164
|
+
seller_type = (
|
|
165
|
+
"dealer"
|
|
166
|
+
if soup.find("div", class_=lambda x: x and "dealer" in str(x).lower())
|
|
167
|
+
else "private"
|
|
168
|
+
)
|
|
169
|
+
data["seller_type"] = seller_type
|
|
170
|
+
|
|
171
|
+
def _extract_ad_id(self, soup: BeautifulSoup, data: dict[str, Any]) -> None:
|
|
172
|
+
ad_info_divs = soup.find_all(
|
|
173
|
+
"div", class_="text-m flex md:flex-row flex-col md:gap-x-56 gap-y-16"
|
|
174
|
+
)
|
|
175
|
+
for div in ad_info_divs:
|
|
176
|
+
ad_id_labels = div.find_all("p", class_="s-text-subtle mb-0")
|
|
177
|
+
for ad_id_label in ad_id_labels:
|
|
178
|
+
if "Annons-ID" in ad_id_label.get_text(strip=True):
|
|
179
|
+
ad_id_elem = ad_id_label.find_next_sibling("p")
|
|
180
|
+
if ad_id_elem:
|
|
181
|
+
data["ad_id"] = ad_id_elem.get_text(strip=True)
|
|
182
|
+
break
|
|
@@ -4,13 +4,16 @@ from dataclasses import dataclass
|
|
|
4
4
|
from typing import Any
|
|
5
5
|
|
|
6
6
|
import httpx
|
|
7
|
+
from httpx import Response
|
|
7
8
|
|
|
9
|
+
from .ad_parser import CarAd, RecommerceAd
|
|
8
10
|
from .constants import (
|
|
9
11
|
HEADERS,
|
|
10
12
|
SITE_URL,
|
|
11
13
|
CarColor,
|
|
12
14
|
CarModel,
|
|
13
15
|
CarSortOrder,
|
|
16
|
+
CarTransmission,
|
|
14
17
|
Category,
|
|
15
18
|
Location,
|
|
16
19
|
SortOrder,
|
|
@@ -28,14 +31,14 @@ def _request(
|
|
|
28
31
|
*,
|
|
29
32
|
url: str,
|
|
30
33
|
params: list[QueryParam],
|
|
31
|
-
) ->
|
|
34
|
+
) -> Response:
|
|
32
35
|
response = httpx.get(
|
|
33
36
|
url,
|
|
34
37
|
headers=HEADERS,
|
|
35
38
|
params=[(param.name, param.value) for param in params],
|
|
36
39
|
)
|
|
37
40
|
response.raise_for_status()
|
|
38
|
-
return response
|
|
41
|
+
return response
|
|
39
42
|
|
|
40
43
|
|
|
41
44
|
@dataclass(frozen=True)
|
|
@@ -61,7 +64,7 @@ class BlocketAPI:
|
|
|
61
64
|
*([QueryParam("sub_category", sub_category.value)] if sub_category else []),
|
|
62
65
|
]
|
|
63
66
|
|
|
64
|
-
return _request(url=url, params=params)
|
|
67
|
+
return _request(url=url, params=params).json()
|
|
65
68
|
|
|
66
69
|
def search_car(
|
|
67
70
|
self,
|
|
@@ -77,7 +80,7 @@ class BlocketAPI:
|
|
|
77
80
|
milage_from: int | None = None,
|
|
78
81
|
milage_to: int | None = None,
|
|
79
82
|
colors: list[CarColor] = [],
|
|
80
|
-
|
|
83
|
+
transmissions: list[CarTransmission] = [],
|
|
81
84
|
) -> Any:
|
|
82
85
|
url = f"{SITE_URL}/mobility/search/api/search/SEARCH_ID_CAR_USED"
|
|
83
86
|
params = [
|
|
@@ -92,7 +95,11 @@ class BlocketAPI:
|
|
|
92
95
|
*([QueryParam("milage_from", milage_from)] if milage_from else []),
|
|
93
96
|
*([QueryParam("milage_to", milage_to)] if milage_to else []),
|
|
94
97
|
*[QueryParam("exterior_colour", color.value) for color in colors],
|
|
95
|
-
*
|
|
98
|
+
*[QueryParam("transmission", t.value) for t in transmissions],
|
|
96
99
|
]
|
|
97
100
|
|
|
98
|
-
return _request(url=url, params=params)
|
|
101
|
+
return _request(url=url, params=params).json()
|
|
102
|
+
|
|
103
|
+
def get_ad(self, ad: RecommerceAd | CarAd) -> dict:
|
|
104
|
+
response = _request(url=ad.url, params=[])
|
|
105
|
+
return ad.parse(response)
|
|
@@ -48,38 +48,26 @@ class Location(StrEnum):
|
|
|
48
48
|
VASTERBOTTEN = "0.300024"
|
|
49
49
|
VASTERNORRLAND = "0.300022"
|
|
50
50
|
VASTMANLAND = "0.300019"
|
|
51
|
-
|
|
51
|
+
VASTRA_GOTALAND = "0.300014"
|
|
52
52
|
OREBRO = "0.300018"
|
|
53
53
|
OSTERGOTLAND = "0.300005"
|
|
54
54
|
|
|
55
55
|
|
|
56
56
|
class Category(StrEnum):
|
|
57
57
|
AFFARSVERKSAMHET = "0.91"
|
|
58
|
-
# BIL_OCH_HUSVAGN = None
|
|
59
|
-
# BAT = None
|
|
60
58
|
DJUR_OCH_TILLBEHOR = "0.77"
|
|
61
59
|
ELEKTRONIK_OCH_VITVAROR = "0.93"
|
|
62
|
-
# ENTREPRENAD__OCH_LANTBRUKSMASKINER = None
|
|
63
60
|
FORDONSTILLBEHOR = "0.90"
|
|
64
61
|
FRITID_HOBBY_OCH_UNDERHALLNING = "0.86"
|
|
65
62
|
FORALDRAR_OCH_BARN = "0.68"
|
|
66
63
|
KLADER_KOSMETIKA_OCH_ACCESSOARER = "0.71"
|
|
67
64
|
KONST_OCH_ANTIKT = "0.76"
|
|
68
|
-
# MOTORCYKLAR = None
|
|
69
65
|
MOBLER_OCH_INREDNING = "0.78"
|
|
70
66
|
SPORT_OCH_FRITID = "0.69"
|
|
71
67
|
TRADGARD_OCH_RENOVERING = "0.67"
|
|
72
68
|
|
|
73
69
|
|
|
74
70
|
class SubCategory(StrEnum):
|
|
75
|
-
BYGGMATERIAL_OCH_RENOVERING = "1.67.3899"
|
|
76
|
-
BILDELAR_OCH_TILLBEHOR = "1.90.82"
|
|
77
|
-
CYKEL = "1.69.3963"
|
|
78
|
-
JAKT_FISKE_OCH_CAMPING = "1.69.3964"
|
|
79
|
-
TRADGARD_OCH_UTEMILJO = "1.67.3901"
|
|
80
|
-
MC_UTRUSTNING_OCH_RESERVDELAR = "1.90.20"
|
|
81
|
-
HUSVAGNS_OCH_HUSBILSDELAR = "1.90.70"
|
|
82
|
-
VERKTYG = "1.67.5219"
|
|
83
71
|
BUTIK_OCH_DETALJHANDEL = "1.91.3108"
|
|
84
72
|
CONTAINRAR_OCH_BARACKER = "1.91.3111"
|
|
85
73
|
DOMANER_OCH_SAJTER = "1.91.3106"
|
|
@@ -91,6 +79,8 @@ class SubCategory(StrEnum):
|
|
|
91
79
|
SCEN = "1.91.3110"
|
|
92
80
|
STORKOK_OCH_RESTAURANG = "1.91.3103"
|
|
93
81
|
VERKSTAD_BYGG_OCH_KONSTRUKTION = "1.91.3102"
|
|
82
|
+
BILDELAR_OCH_TILLBEHOR = "1.90.82"
|
|
83
|
+
HUSVAGNS_OCH_HUSBILSDELAR = "1.90.70"
|
|
94
84
|
SLAP_OCH_TRAILER = "1.90.80"
|
|
95
85
|
BATDELAR_OCH_TILLBEHOR = "1.90.30"
|
|
96
86
|
AKVARIUM = "1.77.3976"
|
|
@@ -119,7 +109,79 @@ class SubCategory(StrEnum):
|
|
|
119
109
|
TV_SPEL_OCH_SPELKONSOLER = "1.93.3905"
|
|
120
110
|
VITVAROR = "1.93.3907"
|
|
121
111
|
ATV_RESERVDELAR = "1.90.3975"
|
|
122
|
-
|
|
112
|
+
MC_UTRUSTNING_OCH_RESERVDELAR = "1.90.20"
|
|
113
|
+
BILJETTER_OCH_RESOR = "1.86.7735"
|
|
114
|
+
BOCKER_OCH_TIDNINGAR = "1.86.5209"
|
|
115
|
+
HANTVERK = "1.86.7734"
|
|
116
|
+
MAT_OCH_DRYCK = "1.86.3972"
|
|
117
|
+
MODELLER_OCH_BYGGSATSER = "1.86.7732"
|
|
118
|
+
MUSIK_OCH_FILM = "1.86.3922"
|
|
119
|
+
MUSIKINSTRUMENT = "1.86.92"
|
|
120
|
+
RADIOSTYRDA_ENHETER = "1.86.7733"
|
|
121
|
+
SAMLAROBJEKT = "1.86.285"
|
|
122
|
+
SALLSKAPS_OCH_BRADSPEL = "1.86.5203"
|
|
123
|
+
BARNBOCKER = "1.68.8369"
|
|
124
|
+
BARNKLADER = "1.68.3913"
|
|
125
|
+
BARNMOBLER = "1.68.3916"
|
|
126
|
+
BARNSKOR = "1.68.3915"
|
|
127
|
+
BARNTILLBEHOR_OCH_SAKERHET = "1.68.3918"
|
|
128
|
+
BARNVAGNAR = "1.68.3914"
|
|
129
|
+
BILBARNSTOLAR_OCH_BABYSKYDD = "1.68.3911"
|
|
130
|
+
GRAVIDKLADER = "1.68.3948"
|
|
131
|
+
INREDNING_TILL_BARNRUM = "1.68.9445"
|
|
132
|
+
LEKSAKER = "1.68.3912"
|
|
133
|
+
ACCESSOARER = "1.71.9481"
|
|
134
|
+
DAMKLADER = "1.71.3941"
|
|
135
|
+
GLASOGON_OCH_SOLGLASOGON = "1.71.8344"
|
|
136
|
+
HERRKLADER = "1.71.3950"
|
|
137
|
+
HUD_HAR_OCH_KROPPSVARD = "1.71.8280"
|
|
138
|
+
KLOCKOR_OCH_ARMBANDSUR = "1.71.3945"
|
|
139
|
+
KOSMETIK = "1.71.8282"
|
|
140
|
+
MASKERADKLADER = "1.71.8349"
|
|
141
|
+
SKOR = "1.71.3949"
|
|
142
|
+
SMYCKEN_OCH_SMYCKESFORVARING = "1.71.7748"
|
|
143
|
+
VASKOR_OCH_PLANBOCKER = "1.71.3946"
|
|
144
|
+
ANTIKA_MOBLER = "1.76.5178"
|
|
145
|
+
KERAMIK_PORSLIN_OCH_GLAS = "1.76.5176"
|
|
146
|
+
KONST = "1.76.5177"
|
|
147
|
+
SILVERFOREMAL_OCH_SILVERBESTICK = "1.76.5179"
|
|
148
|
+
OVRIGA_ANTIKVITETER = "1.76.5175"
|
|
149
|
+
BORD_OCH_STOLAR = "1.78.5196"
|
|
150
|
+
DEKORATION_OCH_PRYDNADER = "1.78.5222"
|
|
151
|
+
GARDEROBER_OCH_FORVARING = "1.78.5198"
|
|
152
|
+
HYLLOR_OCH_BYRAER = "1.78.8345"
|
|
153
|
+
KOKSUTRUSTNING_OCH_PORSLIN = "1.78.5223"
|
|
154
|
+
LAMPOR = "1.78.5181"
|
|
155
|
+
MATTOR_OCH_TEXTILIER = "1.78.5180"
|
|
156
|
+
PYNT_TILL_HOGTIDER_OCH_FEST = "1.78.9760"
|
|
157
|
+
SOFFOR_OCH_FATOLJER = "1.78.7756"
|
|
158
|
+
SANGAR_OCH_MADRASSER = "1.78.5197"
|
|
159
|
+
OVRIGA_MOBLER_OCH_INREDNING = "1.78.3971"
|
|
160
|
+
BOLLSPORTER = "1.69.3961"
|
|
161
|
+
CYKEL = "1.69.3963"
|
|
162
|
+
EXTREMSPORT = "1.69.3938"
|
|
163
|
+
GOLF = "1.69.5164"
|
|
164
|
+
JAKT_FISKE_OCH_CAMPING = "1.69.3964"
|
|
165
|
+
KOSTTILLSKOTT = "1.69.8281"
|
|
166
|
+
RULLSKRIDSKOR_ISHOCKEY_OCH_KONSTAKNING = "1.69.8346"
|
|
167
|
+
SKYTTE = "1.69.3965"
|
|
168
|
+
SUPPORTERPRODUKTER = "1.69.3937"
|
|
169
|
+
TRANINGSKLOCKOR_OCH_AKTIVITETSARMBAND = "1.69.3967"
|
|
170
|
+
TRANINGSKLADER_OCH_SKOR = "1.69.3940"
|
|
171
|
+
TRANINGSUTRUSTNING = "1.69.5166"
|
|
172
|
+
VATTENSPORT = "1.69.7738"
|
|
173
|
+
VINTERSPORT = "1.69.3962"
|
|
174
|
+
OVRIGA_SPORTER = "1.69.3966"
|
|
175
|
+
BADRUM_OCH_BASTU = "1.67.7749"
|
|
176
|
+
BYGGMATERIAL_OCH_RENOVERING = "1.67.3899"
|
|
177
|
+
GARAGEDELAR_OCH_TILLBEHOR = "1.67.8348"
|
|
178
|
+
KOKSINREDNING_OCH_KOKSSTOMMAR = "1.67.3900"
|
|
179
|
+
LARM_OCH_SAKERHET = "1.67.8347"
|
|
180
|
+
TRADGARD_OCH_UTEMILJO = "1.67.3901"
|
|
181
|
+
UTRUSTNING_FOR_FRITIDSHUS = "1.67.3968"
|
|
182
|
+
VERKTYG = "1.67.5219"
|
|
183
|
+
VARME_OCH_VENTILATION = "1.67.5218"
|
|
184
|
+
OVRIGT = "1.67.3969"
|
|
123
185
|
|
|
124
186
|
|
|
125
187
|
class CarModel(StrEnum):
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: blocket_api
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.92
|
|
4
4
|
Summary: A python API wrapper for blocket.se
|
|
5
5
|
Author-email: dunderrrrrr <emil.bjorkroth@gmail.com>
|
|
6
6
|
Requires-Python: >=3.10
|
|
@@ -16,6 +16,7 @@ Requires-Dist: mkdocs>=1.6.1
|
|
|
16
16
|
Requires-Dist: mkdocs-material>=9.6.21
|
|
17
17
|
Requires-Dist: mkdocs-swagger-ui-tag>=0.7.2
|
|
18
18
|
Requires-Dist: websockets>=15.0.1
|
|
19
|
+
Requires-Dist: beautifulsoup4>=4.14.2
|
|
19
20
|
Requires-Dist: ruff>=0.14.5
|
|
20
21
|
Dynamic: license-file
|
|
21
22
|
|
|
@@ -59,8 +60,8 @@ from blocket_api import (
|
|
|
59
60
|
|
|
60
61
|
api = BlocketAPI()
|
|
61
62
|
|
|
62
|
-
#
|
|
63
|
-
|
|
63
|
+
# search all of blocket
|
|
64
|
+
api.search(
|
|
64
65
|
"Vinterdäck Audi",
|
|
65
66
|
sort_order=SortOrder.PRICE_ASC,
|
|
66
67
|
locations=[Location.STOCKHOLM, Location.UPPSALA],
|
|
@@ -68,16 +69,22 @@ result = api.search(
|
|
|
68
69
|
)
|
|
69
70
|
|
|
70
71
|
# search for cars only
|
|
71
|
-
|
|
72
|
+
api.search_car(
|
|
72
73
|
"Audi", # query is optional
|
|
73
74
|
sort_order=CarSortOrder.MILEAGE_ASC,
|
|
74
75
|
models=[CarModel.AUDI],
|
|
75
76
|
colors=[CarColor.GULD],
|
|
76
77
|
price_from=10000,
|
|
77
78
|
price_to=50000,
|
|
78
|
-
|
|
79
|
+
transmissions=[CarTransmission.MANUAL],
|
|
79
80
|
locations=[Location.STOCKHOLM],
|
|
80
81
|
)
|
|
82
|
+
|
|
83
|
+
# Get a recommerce or car ad
|
|
84
|
+
from blocket_api import CarAd, RecommerceAd
|
|
85
|
+
|
|
86
|
+
api.get_ad(RecommerceAd(12345678))
|
|
87
|
+
api.get_ad(CarAd(12345678))
|
|
81
88
|
```
|
|
82
89
|
|
|
83
90
|
## 📝 Notes
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "blocket_api"
|
|
3
|
-
version = "0.3.
|
|
3
|
+
version = "0.3.92"
|
|
4
4
|
description = "A python API wrapper for blocket.se"
|
|
5
5
|
authors = [{name = "dunderrrrrr", email = "emil.bjorkroth@gmail.com"}]
|
|
6
6
|
readme = "README.md"
|
|
@@ -16,6 +16,7 @@ dependencies = [
|
|
|
16
16
|
"mkdocs-material>=9.6.21",
|
|
17
17
|
"mkdocs-swagger-ui-tag>=0.7.2",
|
|
18
18
|
"websockets>=15.0.1",
|
|
19
|
+
"beautifulsoup4>=4.14.2",
|
|
19
20
|
"ruff>=0.14.5",
|
|
20
21
|
]
|
|
21
22
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|