scrapebadger-cli 0.7.0__tar.gz → 0.9.0__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.
- {scrapebadger_cli-0.7.0 → scrapebadger_cli-0.9.0}/PKG-INFO +1 -1
- {scrapebadger_cli-0.7.0 → scrapebadger_cli-0.9.0}/pyproject.toml +1 -1
- scrapebadger_cli-0.9.0/src/scrapebadger_cli/commands/baidu.py +95 -0
- scrapebadger_cli-0.9.0/src/scrapebadger_cli/commands/bing.py +126 -0
- scrapebadger_cli-0.9.0/src/scrapebadger_cli/commands/duckduckgo.py +176 -0
- {scrapebadger_cli-0.7.0 → scrapebadger_cli-0.9.0}/src/scrapebadger_cli/commands/facebook.py +0 -60
- {scrapebadger_cli-0.7.0 → scrapebadger_cli-0.9.0}/src/scrapebadger_cli/commands/google.py +56 -0
- {scrapebadger_cli-0.7.0 → scrapebadger_cli-0.9.0}/src/scrapebadger_cli/commands/instagram.py +28 -28
- scrapebadger_cli-0.9.0/src/scrapebadger_cli/commands/walmart.py +211 -0
- scrapebadger_cli-0.9.0/src/scrapebadger_cli/commands/yandex.py +102 -0
- {scrapebadger_cli-0.7.0 → scrapebadger_cli-0.9.0}/src/scrapebadger_cli/main.py +10 -0
- {scrapebadger_cli-0.7.0 → scrapebadger_cli-0.9.0}/uv.lock +1 -1
- {scrapebadger_cli-0.7.0 → scrapebadger_cli-0.9.0}/.gitignore +0 -0
- {scrapebadger_cli-0.7.0 → scrapebadger_cli-0.9.0}/README.md +0 -0
- {scrapebadger_cli-0.7.0 → scrapebadger_cli-0.9.0}/src/scrapebadger_cli/__init__.py +0 -0
- {scrapebadger_cli-0.7.0 → scrapebadger_cli-0.9.0}/src/scrapebadger_cli/client.py +0 -0
- {scrapebadger_cli-0.7.0 → scrapebadger_cli-0.9.0}/src/scrapebadger_cli/commands/__init__.py +0 -0
- {scrapebadger_cli-0.7.0 → scrapebadger_cli-0.9.0}/src/scrapebadger_cli/commands/amazon.py +0 -0
- {scrapebadger_cli-0.7.0 → scrapebadger_cli-0.9.0}/src/scrapebadger_cli/commands/apartments.py +0 -0
- {scrapebadger_cli-0.7.0 → scrapebadger_cli-0.9.0}/src/scrapebadger_cli/commands/auth.py +0 -0
- {scrapebadger_cli-0.7.0 → scrapebadger_cli-0.9.0}/src/scrapebadger_cli/commands/chatgpt.py +0 -0
- {scrapebadger_cli-0.7.0 → scrapebadger_cli-0.9.0}/src/scrapebadger_cli/commands/depop.py +0 -0
- {scrapebadger_cli-0.7.0 → scrapebadger_cli-0.9.0}/src/scrapebadger_cli/commands/ebay.py +0 -0
- {scrapebadger_cli-0.7.0 → scrapebadger_cli-0.9.0}/src/scrapebadger_cli/commands/idealista.py +0 -0
- {scrapebadger_cli-0.7.0 → scrapebadger_cli-0.9.0}/src/scrapebadger_cli/commands/immobiliare.py +0 -0
- {scrapebadger_cli-0.7.0 → scrapebadger_cli-0.9.0}/src/scrapebadger_cli/commands/leboncoin.py +0 -0
- {scrapebadger_cli-0.7.0 → scrapebadger_cli-0.9.0}/src/scrapebadger_cli/commands/linkedin.py +0 -0
- {scrapebadger_cli-0.7.0 → scrapebadger_cli-0.9.0}/src/scrapebadger_cli/commands/loopnet.py +0 -0
- {scrapebadger_cli-0.7.0 → scrapebadger_cli-0.9.0}/src/scrapebadger_cli/commands/realtor.py +0 -0
- {scrapebadger_cli-0.7.0 → scrapebadger_cli-0.9.0}/src/scrapebadger_cli/commands/reddit.py +0 -0
- {scrapebadger_cli-0.7.0 → scrapebadger_cli-0.9.0}/src/scrapebadger_cli/commands/redfin.py +0 -0
- {scrapebadger_cli-0.7.0 → scrapebadger_cli-0.9.0}/src/scrapebadger_cli/commands/stream.py +0 -0
- {scrapebadger_cli-0.7.0 → scrapebadger_cli-0.9.0}/src/scrapebadger_cli/commands/tiktok.py +0 -0
- {scrapebadger_cli-0.7.0 → scrapebadger_cli-0.9.0}/src/scrapebadger_cli/commands/twitter.py +0 -0
- {scrapebadger_cli-0.7.0 → scrapebadger_cli-0.9.0}/src/scrapebadger_cli/commands/vinted.py +0 -0
- {scrapebadger_cli-0.7.0 → scrapebadger_cli-0.9.0}/src/scrapebadger_cli/commands/web.py +0 -0
- {scrapebadger_cli-0.7.0 → scrapebadger_cli-0.9.0}/src/scrapebadger_cli/commands/youtube.py +0 -0
- {scrapebadger_cli-0.7.0 → scrapebadger_cli-0.9.0}/src/scrapebadger_cli/commands/zillow.py +0 -0
- {scrapebadger_cli-0.7.0 → scrapebadger_cli-0.9.0}/src/scrapebadger_cli/config.py +0 -0
- {scrapebadger_cli-0.7.0 → scrapebadger_cli-0.9.0}/src/scrapebadger_cli/output.py +0 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"""Baidu search engine commands."""
|
|
2
|
+
|
|
3
|
+
import typer
|
|
4
|
+
|
|
5
|
+
from scrapebadger_cli.client import api_get
|
|
6
|
+
from scrapebadger_cli.output import render
|
|
7
|
+
|
|
8
|
+
app = typer.Typer(no_args_is_help=True)
|
|
9
|
+
|
|
10
|
+
FMT = typer.Option("json", "--output", "-o", help="Output format: json, csv, table, markdown")
|
|
11
|
+
FIELDS = typer.Option(None, "--fields", "-f", help="Comma-separated fields to include")
|
|
12
|
+
|
|
13
|
+
_RESULT_COLS = "position,title,url,source,date"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _show(
|
|
17
|
+
data: object, fmt: str, fields: str | None, table_cols: str, key: str | None = None
|
|
18
|
+
) -> None:
|
|
19
|
+
"""Render, defaulting the columns for the human formats only.
|
|
20
|
+
|
|
21
|
+
json and csv keep the whole envelope so a machine-readable export never
|
|
22
|
+
silently drops fields; table/markdown drill to `key` (the response's own
|
|
23
|
+
row list, when `output.render` cannot find it) and default to `table_cols`.
|
|
24
|
+
"""
|
|
25
|
+
if fmt in ("table", "markdown"):
|
|
26
|
+
if key and isinstance(data, dict):
|
|
27
|
+
data = data.get(key, data)
|
|
28
|
+
fields = fields or table_cols
|
|
29
|
+
render(data, fmt, fields)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@app.command()
|
|
33
|
+
def search(
|
|
34
|
+
query: str = typer.Argument(..., help="Search keywords, e.g. '咖啡机'"),
|
|
35
|
+
page: int = typer.Option(1, "--page", "-p", help="1-76"),
|
|
36
|
+
num: int = typer.Option(10, "--num", "-n", help="Results per page, 1-50"),
|
|
37
|
+
language: str | None = typer.Option(
|
|
38
|
+
None, "--language", "-l", help="all, zh-cn (simplified), zh-tw (traditional)"
|
|
39
|
+
),
|
|
40
|
+
time_from: int | None = typer.Option(
|
|
41
|
+
None, "--time-from", help="Unix timestamp — published after"
|
|
42
|
+
),
|
|
43
|
+
time_to: int | None = typer.Option(None, "--time-to", help="Unix timestamp — published before"),
|
|
44
|
+
fmt: str = FMT,
|
|
45
|
+
fields: str | None = FIELDS,
|
|
46
|
+
) -> None:
|
|
47
|
+
"""Search baidu.com — organic results with the real target URL."""
|
|
48
|
+
data = api_get(
|
|
49
|
+
"/v1/baidu/search",
|
|
50
|
+
{
|
|
51
|
+
"query": query,
|
|
52
|
+
"page": page,
|
|
53
|
+
"num": num,
|
|
54
|
+
"language": language,
|
|
55
|
+
"time_from": time_from,
|
|
56
|
+
"time_to": time_to,
|
|
57
|
+
},
|
|
58
|
+
)
|
|
59
|
+
_show(data, fmt, fields, _RESULT_COLS, key="results")
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@app.command()
|
|
63
|
+
def news(
|
|
64
|
+
query: str = typer.Argument(..., help="Search keywords"),
|
|
65
|
+
page: int = typer.Option(1, "--page", "-p", help="1-76"),
|
|
66
|
+
sort: str | None = typer.Option(None, "--sort", "-s", help="relevance or time"),
|
|
67
|
+
fmt: str = FMT,
|
|
68
|
+
fields: str | None = FIELDS,
|
|
69
|
+
) -> None:
|
|
70
|
+
"""Search the Baidu news vertical."""
|
|
71
|
+
data = api_get("/v1/baidu/news", {"query": query, "page": page, "sort": sort})
|
|
72
|
+
_show(data, fmt, fields, _RESULT_COLS, key="results")
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@app.command()
|
|
76
|
+
def images(
|
|
77
|
+
query: str = typer.Argument(..., help="Search keywords"),
|
|
78
|
+
page: int = typer.Option(1, "--page", "-p", help="1-50 (30 images per page)"),
|
|
79
|
+
fmt: str = FMT,
|
|
80
|
+
fields: str | None = FIELDS,
|
|
81
|
+
) -> None:
|
|
82
|
+
"""Search Baidu images."""
|
|
83
|
+
data = api_get("/v1/baidu/images", {"query": query, "page": page})
|
|
84
|
+
_show(data, fmt, fields, "position,title,image_url,width,height,from_url", key="results")
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@app.command()
|
|
88
|
+
def autocomplete(
|
|
89
|
+
query: str = typer.Argument(..., help="Partial search term, e.g. '咖啡'"),
|
|
90
|
+
fmt: str = FMT,
|
|
91
|
+
fields: str | None = FIELDS,
|
|
92
|
+
) -> None:
|
|
93
|
+
"""Get Baidu search-box suggestions."""
|
|
94
|
+
data = api_get("/v1/baidu/autocomplete", {"query": query})
|
|
95
|
+
_show(data, fmt, fields, "query,type", key="suggestions")
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"""Bing search engine commands. market is a language-COUNTRY code, e.g. en-US."""
|
|
2
|
+
|
|
3
|
+
import typer
|
|
4
|
+
|
|
5
|
+
from scrapebadger_cli.client import api_get
|
|
6
|
+
from scrapebadger_cli.output import render
|
|
7
|
+
|
|
8
|
+
app = typer.Typer(no_args_is_help=True)
|
|
9
|
+
|
|
10
|
+
FMT = typer.Option("json", "--output", "-o", help="Output format: json, csv, table, markdown")
|
|
11
|
+
FIELDS = typer.Option(None, "--fields", "-f", help="Comma-separated fields to include")
|
|
12
|
+
|
|
13
|
+
MARKET = typer.Option("en-US", "--market", "-m", help="Bing market code, e.g. en-US")
|
|
14
|
+
SAFE_HELP = "off, moderate, strict"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _show(
|
|
18
|
+
data: object, fmt: str, fields: str | None, table_cols: str, key: str | None = None
|
|
19
|
+
) -> None:
|
|
20
|
+
"""Render, defaulting the columns for the human formats only.
|
|
21
|
+
|
|
22
|
+
json and csv keep the whole envelope so a machine-readable export never
|
|
23
|
+
silently drops fields; table/markdown drill to `key` (the response's own
|
|
24
|
+
row list, when `output.render` cannot find it) and default to `table_cols`.
|
|
25
|
+
"""
|
|
26
|
+
if fmt in ("table", "markdown"):
|
|
27
|
+
if key and isinstance(data, dict):
|
|
28
|
+
data = data.get(key, data)
|
|
29
|
+
fields = fields or table_cols
|
|
30
|
+
render(data, fmt, fields)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@app.command()
|
|
34
|
+
def search(
|
|
35
|
+
query: str = typer.Argument(..., help="Search keywords, e.g. 'coffee machine'"),
|
|
36
|
+
market: str = MARKET,
|
|
37
|
+
count: int = typer.Option(10, "--count", "-c", help="Results per page, 1-50"),
|
|
38
|
+
offset: int = typer.Option(0, "--offset", help="Zero-based result offset"),
|
|
39
|
+
safe_search: str | None = typer.Option(None, "--safe-search", help=SAFE_HELP),
|
|
40
|
+
fmt: str = FMT,
|
|
41
|
+
fields: str | None = FIELDS,
|
|
42
|
+
) -> None:
|
|
43
|
+
"""Search the Bing web SERP: organic results, ads and related searches."""
|
|
44
|
+
data = api_get(
|
|
45
|
+
"/v1/bing/search",
|
|
46
|
+
{
|
|
47
|
+
"query": query,
|
|
48
|
+
"market": market,
|
|
49
|
+
"count": count,
|
|
50
|
+
"offset": offset,
|
|
51
|
+
"safe_search": safe_search,
|
|
52
|
+
},
|
|
53
|
+
)
|
|
54
|
+
_show(data, fmt, fields, "position,title,display_url,snippet")
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@app.command()
|
|
58
|
+
def images(
|
|
59
|
+
query: str = typer.Argument(..., help="Search keywords, e.g. 'golden retriever'"),
|
|
60
|
+
market: str = MARKET,
|
|
61
|
+
count: int = typer.Option(35, "--count", "-c", help="Results to return"),
|
|
62
|
+
safe_search: str | None = typer.Option(None, "--safe-search", help=SAFE_HELP),
|
|
63
|
+
fmt: str = FMT,
|
|
64
|
+
fields: str | None = FIELDS,
|
|
65
|
+
) -> None:
|
|
66
|
+
"""Search Bing Images: full-size URL, thumbnail and source page."""
|
|
67
|
+
data = api_get(
|
|
68
|
+
"/v1/bing/images",
|
|
69
|
+
{"query": query, "market": market, "count": count, "safe_search": safe_search},
|
|
70
|
+
)
|
|
71
|
+
_show(data, fmt, fields, "position,title,image_url,source_domain,width,height")
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@app.command()
|
|
75
|
+
def videos(
|
|
76
|
+
query: str = typer.Argument(..., help="Search keywords, e.g. 'espresso tutorial'"),
|
|
77
|
+
market: str = MARKET,
|
|
78
|
+
count: int = typer.Option(35, "--count", "-c", help="Results to return"),
|
|
79
|
+
safe_search: str | None = typer.Option(None, "--safe-search", help=SAFE_HELP),
|
|
80
|
+
fmt: str = FMT,
|
|
81
|
+
fields: str | None = FIELDS,
|
|
82
|
+
) -> None:
|
|
83
|
+
"""Search Bing Videos: title, duration, publisher and source platform."""
|
|
84
|
+
data = api_get(
|
|
85
|
+
"/v1/bing/videos",
|
|
86
|
+
{"query": query, "market": market, "count": count, "safe_search": safe_search},
|
|
87
|
+
)
|
|
88
|
+
_show(data, fmt, fields, "position,title,duration,source,publisher,views,published")
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@app.command()
|
|
92
|
+
def news(
|
|
93
|
+
query: str = typer.Argument(..., help="Search keywords, e.g. 'interest rates'"),
|
|
94
|
+
market: str = MARKET,
|
|
95
|
+
freshness: str | None = typer.Option(None, "--freshness", help="day, week, month"),
|
|
96
|
+
fmt: str = FMT,
|
|
97
|
+
fields: str | None = FIELDS,
|
|
98
|
+
) -> None:
|
|
99
|
+
"""Search Bing News: headline, source and absolute publish time."""
|
|
100
|
+
data = api_get(
|
|
101
|
+
"/v1/bing/news",
|
|
102
|
+
{"query": query, "market": market, "freshness": freshness},
|
|
103
|
+
)
|
|
104
|
+
_show(data, fmt, fields, "position,title,source,published_at")
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
@app.command()
|
|
108
|
+
def autocomplete(
|
|
109
|
+
query: str = typer.Argument(..., help="Partial search term, e.g. 'coff'"),
|
|
110
|
+
market: str = MARKET,
|
|
111
|
+
fmt: str = FMT,
|
|
112
|
+
fields: str | None = FIELDS,
|
|
113
|
+
) -> None:
|
|
114
|
+
"""Get Bing search-box query suggestions."""
|
|
115
|
+
data = api_get("/v1/bing/autocomplete", {"query": query, "market": market})
|
|
116
|
+
_show(data, fmt, fields, "value", key="suggestions")
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
@app.command()
|
|
120
|
+
def markets(
|
|
121
|
+
fmt: str = FMT,
|
|
122
|
+
fields: str | None = FIELDS,
|
|
123
|
+
) -> None:
|
|
124
|
+
"""List the curated Bing market codes (30+). Free."""
|
|
125
|
+
data = api_get("/v1/bing/markets")
|
|
126
|
+
_show(data, fmt, fields, "code,name,country", key="markets")
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
"""DuckDuckGo search commands. region is a DuckDuckGo kl code (wt-wt = all regions)."""
|
|
2
|
+
|
|
3
|
+
import typer
|
|
4
|
+
|
|
5
|
+
from scrapebadger_cli.client import api_get
|
|
6
|
+
from scrapebadger_cli.output import render
|
|
7
|
+
|
|
8
|
+
app = typer.Typer(no_args_is_help=True)
|
|
9
|
+
|
|
10
|
+
FMT = typer.Option("json", "--output", "-o", help="Output format: json, csv, table, markdown")
|
|
11
|
+
FIELDS = typer.Option(None, "--fields", "-f", help="Comma-separated fields to include")
|
|
12
|
+
|
|
13
|
+
REGION = typer.Option(
|
|
14
|
+
"wt-wt", "--region", "-r", help="DuckDuckGo region code (kl), e.g. us-en. wt-wt = all regions"
|
|
15
|
+
)
|
|
16
|
+
SAFESEARCH = typer.Option("moderate", "--safesearch", help="on, moderate, off")
|
|
17
|
+
TIMELIMIT = typer.Option(None, "--timelimit", "-t", help="day, week, month, year")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _show(
|
|
21
|
+
data: object, fmt: str, fields: str | None, table_cols: str, key: str | None = None
|
|
22
|
+
) -> None:
|
|
23
|
+
"""Render, defaulting the columns for the human formats only.
|
|
24
|
+
|
|
25
|
+
json and csv keep the whole envelope so a machine-readable export never
|
|
26
|
+
silently drops fields; table/markdown drill to `key` (the response's own
|
|
27
|
+
row list, when `output.render` cannot find it) and default to `table_cols`.
|
|
28
|
+
"""
|
|
29
|
+
if fmt in ("table", "markdown"):
|
|
30
|
+
if key and isinstance(data, dict):
|
|
31
|
+
data = data.get(key, data)
|
|
32
|
+
fields = fields or table_cols
|
|
33
|
+
render(data, fmt, fields)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@app.command()
|
|
37
|
+
def search(
|
|
38
|
+
query: str = typer.Argument(..., help="Search query, e.g. 'web scraping'"),
|
|
39
|
+
region: str = REGION,
|
|
40
|
+
safesearch: str = SAFESEARCH,
|
|
41
|
+
timelimit: str | None = TIMELIMIT,
|
|
42
|
+
page: int = typer.Option(1, "--page", "-p", help="1-25"),
|
|
43
|
+
fmt: str = FMT,
|
|
44
|
+
fields: str | None = FIELDS,
|
|
45
|
+
) -> None:
|
|
46
|
+
"""Search the DuckDuckGo web SERP (organic results, ads flagged is_ad)."""
|
|
47
|
+
data = api_get(
|
|
48
|
+
"/v1/duckduckgo/search",
|
|
49
|
+
{
|
|
50
|
+
"query": query,
|
|
51
|
+
"region": region,
|
|
52
|
+
"safesearch": safesearch,
|
|
53
|
+
"timelimit": timelimit,
|
|
54
|
+
"page": page,
|
|
55
|
+
},
|
|
56
|
+
)
|
|
57
|
+
_show(data, fmt, fields, "title,url,display_url,is_ad", key="results")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@app.command()
|
|
61
|
+
def images(
|
|
62
|
+
query: str = typer.Argument(..., help="Search query, e.g. 'sunset'"),
|
|
63
|
+
region: str = REGION,
|
|
64
|
+
safesearch: str = SAFESEARCH,
|
|
65
|
+
page: int = typer.Option(1, "--page", "-p", help="1-10 (100 results per page)"),
|
|
66
|
+
size: str | None = typer.Option(None, "--size", help="Small, Medium, Large, Wallpaper"),
|
|
67
|
+
color: str | None = typer.Option(None, "--color", help="color, Monochrome, Red, Blue, …"),
|
|
68
|
+
image_type: str | None = typer.Option(
|
|
69
|
+
None, "--type", help="photo, clipart, gif, transparent, line"
|
|
70
|
+
),
|
|
71
|
+
layout: str | None = typer.Option(None, "--layout", help="Square, Tall, Wide"),
|
|
72
|
+
license: str | None = typer.Option(
|
|
73
|
+
None, "--license", help="Any, Public, Share, ShareCommercially, Modify"
|
|
74
|
+
),
|
|
75
|
+
fmt: str = FMT,
|
|
76
|
+
fields: str | None = FIELDS,
|
|
77
|
+
) -> None:
|
|
78
|
+
"""Search DuckDuckGo images with size/color/type/layout/license filters."""
|
|
79
|
+
data = api_get(
|
|
80
|
+
"/v1/duckduckgo/images",
|
|
81
|
+
{
|
|
82
|
+
"query": query,
|
|
83
|
+
"region": region,
|
|
84
|
+
"safesearch": safesearch,
|
|
85
|
+
"page": page,
|
|
86
|
+
"size": size,
|
|
87
|
+
"color": color,
|
|
88
|
+
"image_type": image_type,
|
|
89
|
+
"layout": layout,
|
|
90
|
+
"license": license,
|
|
91
|
+
},
|
|
92
|
+
)
|
|
93
|
+
_show(data, fmt, fields, "title,image,width,height,source", key="results")
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@app.command()
|
|
97
|
+
def news(
|
|
98
|
+
query: str = typer.Argument(..., help="Search query, e.g. 'artificial intelligence'"),
|
|
99
|
+
region: str = REGION,
|
|
100
|
+
safesearch: str = SAFESEARCH,
|
|
101
|
+
timelimit: str | None = TIMELIMIT,
|
|
102
|
+
page: int = typer.Option(1, "--page", "-p", help="1-10 (30 results per page)"),
|
|
103
|
+
fmt: str = FMT,
|
|
104
|
+
fields: str | None = FIELDS,
|
|
105
|
+
) -> None:
|
|
106
|
+
"""Search DuckDuckGo news (headline, source, excerpt, dates, image)."""
|
|
107
|
+
data = api_get(
|
|
108
|
+
"/v1/duckduckgo/news",
|
|
109
|
+
{
|
|
110
|
+
"query": query,
|
|
111
|
+
"region": region,
|
|
112
|
+
"safesearch": safesearch,
|
|
113
|
+
"timelimit": timelimit,
|
|
114
|
+
"page": page,
|
|
115
|
+
},
|
|
116
|
+
)
|
|
117
|
+
_show(data, fmt, fields, "title,source,relative_time,url", key="results")
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
@app.command()
|
|
121
|
+
def videos(
|
|
122
|
+
query: str = typer.Argument(..., help="Search query, e.g. 'python tutorial'"),
|
|
123
|
+
region: str = REGION,
|
|
124
|
+
safesearch: str = SAFESEARCH,
|
|
125
|
+
page: int = typer.Option(1, "--page", "-p", help="1-10 (60 results per page)"),
|
|
126
|
+
duration: str | None = typer.Option(None, "--duration", help="short, medium, long"),
|
|
127
|
+
resolution: str | None = typer.Option(None, "--resolution", help="high, standard"),
|
|
128
|
+
fmt: str = FMT,
|
|
129
|
+
fields: str | None = FIELDS,
|
|
130
|
+
) -> None:
|
|
131
|
+
"""Search DuckDuckGo videos (title, publisher, duration, views)."""
|
|
132
|
+
data = api_get(
|
|
133
|
+
"/v1/duckduckgo/videos",
|
|
134
|
+
{
|
|
135
|
+
"query": query,
|
|
136
|
+
"region": region,
|
|
137
|
+
"safesearch": safesearch,
|
|
138
|
+
"page": page,
|
|
139
|
+
"duration": duration,
|
|
140
|
+
"resolution": resolution,
|
|
141
|
+
},
|
|
142
|
+
)
|
|
143
|
+
_show(data, fmt, fields, "title,publisher,duration,view_count,content", key="results")
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
@app.command()
|
|
147
|
+
def autocomplete(
|
|
148
|
+
query: str = typer.Argument(..., help="Partial query, e.g. 'web scra'"),
|
|
149
|
+
region: str = REGION,
|
|
150
|
+
fmt: str = FMT,
|
|
151
|
+
fields: str | None = FIELDS,
|
|
152
|
+
) -> None:
|
|
153
|
+
"""Get DuckDuckGo search-box suggestions."""
|
|
154
|
+
data = api_get("/v1/duckduckgo/autocomplete", {"query": query, "region": region})
|
|
155
|
+
_show(data, fmt, fields, "suggestions", key="suggestions")
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
@app.command()
|
|
159
|
+
def instant(
|
|
160
|
+
query: str = typer.Argument(..., help="Query for the Instant Answer API, e.g. 'Nikola Tesla'"),
|
|
161
|
+
fmt: str = FMT,
|
|
162
|
+
fields: str | None = FIELDS,
|
|
163
|
+
) -> None:
|
|
164
|
+
"""Query the DuckDuckGo Instant Answer API (abstract, answer, definition)."""
|
|
165
|
+
data = api_get("/v1/duckduckgo/instant", {"query": query})
|
|
166
|
+
_show(data, fmt, fields, "heading,abstract_text,abstract_source,answer,type")
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
@app.command()
|
|
170
|
+
def regions(
|
|
171
|
+
fmt: str = FMT,
|
|
172
|
+
fields: str | None = FIELDS,
|
|
173
|
+
) -> None:
|
|
174
|
+
"""List all supported DuckDuckGo region (kl) codes."""
|
|
175
|
+
data = api_get("/v1/duckduckgo/regions")
|
|
176
|
+
_show(data, fmt, fields, "code,name", key="regions")
|
|
@@ -109,66 +109,6 @@ def categories(
|
|
|
109
109
|
render(data, fmt, fields)
|
|
110
110
|
|
|
111
111
|
|
|
112
|
-
@app.command()
|
|
113
|
-
def search(
|
|
114
|
-
q: str = typer.Argument(..., help="Search query"),
|
|
115
|
-
after: str | None = AFTER,
|
|
116
|
-
fmt: str = FMT,
|
|
117
|
-
fields: str | None = FIELDS,
|
|
118
|
-
) -> None:
|
|
119
|
-
"""Search Facebook globally (pages, people, groups, posts)."""
|
|
120
|
-
data = api_get("/v1/facebook/search", {"q": q, "after": after})
|
|
121
|
-
render(data, fmt, fields)
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
@app.command(name="search-pages")
|
|
125
|
-
def search_pages(
|
|
126
|
-
q: str = typer.Argument(..., help="Search query"),
|
|
127
|
-
after: str | None = AFTER,
|
|
128
|
-
fmt: str = FMT,
|
|
129
|
-
fields: str | None = FIELDS,
|
|
130
|
-
) -> None:
|
|
131
|
-
"""Search Facebook Pages."""
|
|
132
|
-
data = api_get("/v1/facebook/search/pages", {"q": q, "after": after})
|
|
133
|
-
render(data, fmt, fields)
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
@app.command(name="search-people")
|
|
137
|
-
def search_people(
|
|
138
|
-
q: str = typer.Argument(..., help="Search query"),
|
|
139
|
-
after: str | None = AFTER,
|
|
140
|
-
fmt: str = FMT,
|
|
141
|
-
fields: str | None = FIELDS,
|
|
142
|
-
) -> None:
|
|
143
|
-
"""Search Facebook profiles."""
|
|
144
|
-
data = api_get("/v1/facebook/search/people", {"q": q, "after": after})
|
|
145
|
-
render(data, fmt, fields)
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
@app.command(name="search-groups")
|
|
149
|
-
def search_groups(
|
|
150
|
-
q: str = typer.Argument(..., help="Search query"),
|
|
151
|
-
after: str | None = AFTER,
|
|
152
|
-
fmt: str = FMT,
|
|
153
|
-
fields: str | None = FIELDS,
|
|
154
|
-
) -> None:
|
|
155
|
-
"""Search Facebook groups."""
|
|
156
|
-
data = api_get("/v1/facebook/search/groups", {"q": q, "after": after})
|
|
157
|
-
render(data, fmt, fields)
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
@app.command(name="search-posts")
|
|
161
|
-
def search_posts(
|
|
162
|
-
q: str = typer.Argument(..., help="Search query"),
|
|
163
|
-
after: str | None = AFTER,
|
|
164
|
-
fmt: str = FMT,
|
|
165
|
-
fields: str | None = FIELDS,
|
|
166
|
-
) -> None:
|
|
167
|
-
"""Search public Facebook posts."""
|
|
168
|
-
data = api_get("/v1/facebook/search/posts", {"q": q, "after": after})
|
|
169
|
-
render(data, fmt, fields)
|
|
170
|
-
|
|
171
|
-
|
|
172
112
|
@app.command()
|
|
173
113
|
def page(
|
|
174
114
|
identifier: str = typer.Argument(..., help="Page username or numeric page ID"),
|
|
@@ -1123,3 +1123,59 @@ def flights_search(
|
|
|
1123
1123
|
},
|
|
1124
1124
|
)
|
|
1125
1125
|
render(data, fmt, fields)
|
|
1126
|
+
|
|
1127
|
+
|
|
1128
|
+
@app.command(name="flights-calendar")
|
|
1129
|
+
def flights_calendar(
|
|
1130
|
+
departure_id: str = typer.Argument(..., help="Departure airport IATA (e.g. JFK)"),
|
|
1131
|
+
arrival_id: str = typer.Argument(..., help="Arrival airport IATA (e.g. LHR)"),
|
|
1132
|
+
outbound_date_from: str = typer.Argument(..., help="First outbound date (YYYY-MM-DD)"),
|
|
1133
|
+
outbound_date_to: str = typer.Argument(
|
|
1134
|
+
..., help="Last outbound date (YYYY-MM-DD); <=200 days, or <=14 in date-grid mode"
|
|
1135
|
+
),
|
|
1136
|
+
trip_type: str | None = typer.Option(
|
|
1137
|
+
None, "--trip-type", help="one_way (default) | round_trip"
|
|
1138
|
+
),
|
|
1139
|
+
trip_length_days: int | None = typer.Option(
|
|
1140
|
+
None, "--trip-length-days", help="Round-trip stay length in nights (default 7)"
|
|
1141
|
+
),
|
|
1142
|
+
return_date_from: str | None = typer.Option(
|
|
1143
|
+
None,
|
|
1144
|
+
"--return-date-from",
|
|
1145
|
+
help="Date-grid mode: first return date (with --return-date-to, <=14 days)",
|
|
1146
|
+
),
|
|
1147
|
+
return_date_to: str | None = typer.Option(
|
|
1148
|
+
None, "--return-date-to", help="Date-grid mode: last return date"
|
|
1149
|
+
),
|
|
1150
|
+
adults: int | None = typer.Option(None, "--adults", help="Adult passengers (1-9)"),
|
|
1151
|
+
children: int | None = typer.Option(None, "--children", help="Children passengers (0-8)"),
|
|
1152
|
+
travel_class: str | None = typer.Option(
|
|
1153
|
+
None, "--travel-class", help="economy | premium_economy | business | first"
|
|
1154
|
+
),
|
|
1155
|
+
currency: str | None = typer.Option(None, "--currency", help="ISO-4217 currency code"),
|
|
1156
|
+
gl: str | None = typer.Option(None, "--gl", help="Country code"),
|
|
1157
|
+
hl: str | None = typer.Option(None, "--hl", help="Language code"),
|
|
1158
|
+
fmt: str = FMT,
|
|
1159
|
+
fields: str | None = FIELDS,
|
|
1160
|
+
) -> None:
|
|
1161
|
+
"""Cheapest fare per date — up to 200 dates in one call (price graph / date grid)."""
|
|
1162
|
+
data = api_get(
|
|
1163
|
+
"/v1/google/flights/calendar",
|
|
1164
|
+
{
|
|
1165
|
+
"departure_id": departure_id,
|
|
1166
|
+
"arrival_id": arrival_id,
|
|
1167
|
+
"outbound_date_from": outbound_date_from,
|
|
1168
|
+
"outbound_date_to": outbound_date_to,
|
|
1169
|
+
"trip_type": trip_type,
|
|
1170
|
+
"trip_length_days": trip_length_days,
|
|
1171
|
+
"return_date_from": return_date_from,
|
|
1172
|
+
"return_date_to": return_date_to,
|
|
1173
|
+
"adults": adults,
|
|
1174
|
+
"children": children,
|
|
1175
|
+
"travel_class": travel_class,
|
|
1176
|
+
"currency": currency,
|
|
1177
|
+
"gl": gl,
|
|
1178
|
+
"hl": hl,
|
|
1179
|
+
},
|
|
1180
|
+
)
|
|
1181
|
+
render(data, fmt, fields)
|
{scrapebadger_cli-0.7.0 → scrapebadger_cli-0.9.0}/src/scrapebadger_cli/commands/instagram.py
RENAMED
|
@@ -33,7 +33,7 @@ def about(
|
|
|
33
33
|
fmt: str = FMT,
|
|
34
34
|
fields: str | None = FIELDS,
|
|
35
35
|
) -> None:
|
|
36
|
-
"""Get 'About this account' info."""
|
|
36
|
+
"""[temporarily unavailable] Get 'About this account' info."""
|
|
37
37
|
data = api_get(f"/v1/instagram/users/{username}/about")
|
|
38
38
|
render(data, fmt, fields)
|
|
39
39
|
|
|
@@ -73,7 +73,7 @@ def videos(
|
|
|
73
73
|
fmt: str = FMT,
|
|
74
74
|
fields: str | None = FIELDS,
|
|
75
75
|
) -> None:
|
|
76
|
-
"""Get a user's videos."""
|
|
76
|
+
"""[temporarily unavailable] Get a user's videos."""
|
|
77
77
|
data = api_get(f"/v1/instagram/users/{username}/videos", {"amount": amount, "cursor": cursor})
|
|
78
78
|
render(data, fmt, fields)
|
|
79
79
|
|
|
@@ -86,7 +86,7 @@ def reels(
|
|
|
86
86
|
fmt: str = FMT,
|
|
87
87
|
fields: str | None = FIELDS,
|
|
88
88
|
) -> None:
|
|
89
|
-
"""Get a user's reels."""
|
|
89
|
+
"""[temporarily unavailable] Get a user's reels."""
|
|
90
90
|
data = api_get(f"/v1/instagram/users/{username}/reels", {"amount": amount, "cursor": cursor})
|
|
91
91
|
render(data, fmt, fields)
|
|
92
92
|
|
|
@@ -99,7 +99,7 @@ def tagged(
|
|
|
99
99
|
fmt: str = FMT,
|
|
100
100
|
fields: str | None = FIELDS,
|
|
101
101
|
) -> None:
|
|
102
|
-
"""Get media a user is tagged in."""
|
|
102
|
+
"""[temporarily unavailable] Get media a user is tagged in."""
|
|
103
103
|
data = api_get(f"/v1/instagram/users/{username}/tagged", {"amount": amount, "cursor": cursor})
|
|
104
104
|
render(data, fmt, fields)
|
|
105
105
|
|
|
@@ -110,7 +110,7 @@ def pinned(
|
|
|
110
110
|
fmt: str = FMT,
|
|
111
111
|
fields: str | None = FIELDS,
|
|
112
112
|
) -> None:
|
|
113
|
-
"""Get a user's pinned posts."""
|
|
113
|
+
"""[temporarily unavailable] Get a user's pinned posts."""
|
|
114
114
|
data = api_get(f"/v1/instagram/users/{username}/pinned")
|
|
115
115
|
render(data, fmt, fields)
|
|
116
116
|
|
|
@@ -152,7 +152,7 @@ def followers(
|
|
|
152
152
|
fmt: str = FMT,
|
|
153
153
|
fields: str | None = FIELDS,
|
|
154
154
|
) -> None:
|
|
155
|
-
"""Get a user's followers."""
|
|
155
|
+
"""[temporarily unavailable] Get a user's followers."""
|
|
156
156
|
data = api_get(
|
|
157
157
|
f"/v1/instagram/users/{username}/followers",
|
|
158
158
|
{"amount": amount, "cursor": cursor, "order": order},
|
|
@@ -168,7 +168,7 @@ def following(
|
|
|
168
168
|
fmt: str = FMT,
|
|
169
169
|
fields: str | None = FIELDS,
|
|
170
170
|
) -> None:
|
|
171
|
-
"""Get accounts a user follows."""
|
|
171
|
+
"""[temporarily unavailable] Get accounts a user follows."""
|
|
172
172
|
data = api_get(
|
|
173
173
|
f"/v1/instagram/users/{username}/following", {"amount": amount, "cursor": cursor}
|
|
174
174
|
)
|
|
@@ -184,7 +184,7 @@ def search_followers(
|
|
|
184
184
|
fmt: str = FMT,
|
|
185
185
|
fields: str | None = FIELDS,
|
|
186
186
|
) -> None:
|
|
187
|
-
"""Search within a user's followers."""
|
|
187
|
+
"""[temporarily unavailable] Search within a user's followers."""
|
|
188
188
|
data = api_get(
|
|
189
189
|
f"/v1/instagram/users/{username}/followers/search",
|
|
190
190
|
{"query": query, "amount": amount, "cursor": cursor},
|
|
@@ -201,7 +201,7 @@ def stories(
|
|
|
201
201
|
fmt: str = FMT,
|
|
202
202
|
fields: str | None = FIELDS,
|
|
203
203
|
) -> None:
|
|
204
|
-
"""Get a user's active stories."""
|
|
204
|
+
"""[temporarily unavailable] Get a user's active stories."""
|
|
205
205
|
data = api_get(f"/v1/instagram/users/{username}/stories")
|
|
206
206
|
render(data, fmt, fields)
|
|
207
207
|
|
|
@@ -212,7 +212,7 @@ def highlights(
|
|
|
212
212
|
fmt: str = FMT,
|
|
213
213
|
fields: str | None = FIELDS,
|
|
214
214
|
) -> None:
|
|
215
|
-
"""Get a user's highlight reels."""
|
|
215
|
+
"""[temporarily unavailable] Get a user's highlight reels."""
|
|
216
216
|
data = api_get(f"/v1/instagram/users/{username}/highlights")
|
|
217
217
|
render(data, fmt, fields)
|
|
218
218
|
|
|
@@ -223,7 +223,7 @@ def story(
|
|
|
223
223
|
fmt: str = FMT,
|
|
224
224
|
fields: str | None = FIELDS,
|
|
225
225
|
) -> None:
|
|
226
|
-
"""Get a single story."""
|
|
226
|
+
"""[temporarily unavailable] Get a single story."""
|
|
227
227
|
data = api_get(f"/v1/instagram/stories/{story_pk}")
|
|
228
228
|
render(data, fmt, fields)
|
|
229
229
|
|
|
@@ -234,7 +234,7 @@ def highlight(
|
|
|
234
234
|
fmt: str = FMT,
|
|
235
235
|
fields: str | None = FIELDS,
|
|
236
236
|
) -> None:
|
|
237
|
-
"""Get a highlight reel and its items."""
|
|
237
|
+
"""[temporarily unavailable] Get a highlight reel and its items."""
|
|
238
238
|
data = api_get(f"/v1/instagram/highlights/{highlight_pk}")
|
|
239
239
|
render(data, fmt, fields)
|
|
240
240
|
|
|
@@ -264,7 +264,7 @@ def comment_replies(
|
|
|
264
264
|
fmt: str = FMT,
|
|
265
265
|
fields: str | None = FIELDS,
|
|
266
266
|
) -> None:
|
|
267
|
-
"""Get replies to a comment."""
|
|
267
|
+
"""[temporarily unavailable] Get replies to a comment."""
|
|
268
268
|
data = api_get(
|
|
269
269
|
f"/v1/instagram/media/{code}/comments/{comment_id}/replies",
|
|
270
270
|
{"amount": amount, "cursor": cursor},
|
|
@@ -297,7 +297,7 @@ def likers(
|
|
|
297
297
|
fmt: str = FMT,
|
|
298
298
|
fields: str | None = FIELDS,
|
|
299
299
|
) -> None:
|
|
300
|
-
"""Get users who liked a media item."""
|
|
300
|
+
"""[temporarily unavailable] Get users who liked a media item."""
|
|
301
301
|
data = api_get(f"/v1/instagram/media/{code}/likers", {"amount": amount, "cursor": cursor})
|
|
302
302
|
render(data, fmt, fields)
|
|
303
303
|
|
|
@@ -324,7 +324,7 @@ def hashtag_top(
|
|
|
324
324
|
fmt: str = FMT,
|
|
325
325
|
fields: str | None = FIELDS,
|
|
326
326
|
) -> None:
|
|
327
|
-
"""Get top media for a hashtag."""
|
|
327
|
+
"""[temporarily unavailable] Get top media for a hashtag."""
|
|
328
328
|
data = api_get(f"/v1/instagram/hashtags/{tag}/top", {"amount": amount, "cursor": cursor})
|
|
329
329
|
render(data, fmt, fields)
|
|
330
330
|
|
|
@@ -350,7 +350,7 @@ def hashtag_reels(
|
|
|
350
350
|
fmt: str = FMT,
|
|
351
351
|
fields: str | None = FIELDS,
|
|
352
352
|
) -> None:
|
|
353
|
-
"""Get reels for a hashtag."""
|
|
353
|
+
"""[temporarily unavailable] Get reels for a hashtag."""
|
|
354
354
|
data = api_get(f"/v1/instagram/hashtags/{tag}/reels", {"amount": amount, "cursor": cursor})
|
|
355
355
|
render(data, fmt, fields)
|
|
356
356
|
|
|
@@ -364,7 +364,7 @@ def location(
|
|
|
364
364
|
fmt: str = FMT,
|
|
365
365
|
fields: str | None = FIELDS,
|
|
366
366
|
) -> None:
|
|
367
|
-
"""Get location metadata."""
|
|
367
|
+
"""[temporarily unavailable] Get location metadata."""
|
|
368
368
|
data = api_get(f"/v1/instagram/locations/{location_pk}")
|
|
369
369
|
render(data, fmt, fields)
|
|
370
370
|
|
|
@@ -377,7 +377,7 @@ def location_top(
|
|
|
377
377
|
fmt: str = FMT,
|
|
378
378
|
fields: str | None = FIELDS,
|
|
379
379
|
) -> None:
|
|
380
|
-
"""Get top media for a location."""
|
|
380
|
+
"""[temporarily unavailable] Get top media for a location."""
|
|
381
381
|
data = api_get(
|
|
382
382
|
f"/v1/instagram/locations/{location_pk}/top", {"amount": amount, "cursor": cursor}
|
|
383
383
|
)
|
|
@@ -392,7 +392,7 @@ def location_recent(
|
|
|
392
392
|
fmt: str = FMT,
|
|
393
393
|
fields: str | None = FIELDS,
|
|
394
394
|
) -> None:
|
|
395
|
-
"""Get recent media for a location."""
|
|
395
|
+
"""[temporarily unavailable] Get recent media for a location."""
|
|
396
396
|
data = api_get(
|
|
397
397
|
f"/v1/instagram/locations/{location_pk}/recent", {"amount": amount, "cursor": cursor}
|
|
398
398
|
)
|
|
@@ -407,7 +407,7 @@ def location_search(
|
|
|
407
407
|
fmt: str = FMT,
|
|
408
408
|
fields: str | None = FIELDS,
|
|
409
409
|
) -> None:
|
|
410
|
-
"""Search locations by name or coordinates."""
|
|
410
|
+
"""[temporarily unavailable] Search locations by name or coordinates."""
|
|
411
411
|
data = api_get("/v1/instagram/locations/search", {"query": query, "lat": lat, "lng": lng})
|
|
412
412
|
render(data, fmt, fields)
|
|
413
413
|
|
|
@@ -421,7 +421,7 @@ def search_users(
|
|
|
421
421
|
fmt: str = FMT,
|
|
422
422
|
fields: str | None = FIELDS,
|
|
423
423
|
) -> None:
|
|
424
|
-
"""Search users."""
|
|
424
|
+
"""[temporarily unavailable] Search users."""
|
|
425
425
|
data = api_get("/v1/instagram/search/users", {"query": query})
|
|
426
426
|
render(data, fmt, fields)
|
|
427
427
|
|
|
@@ -445,7 +445,7 @@ def search_places(
|
|
|
445
445
|
fmt: str = FMT,
|
|
446
446
|
fields: str | None = FIELDS,
|
|
447
447
|
) -> None:
|
|
448
|
-
"""Search places by name or coordinates."""
|
|
448
|
+
"""[temporarily unavailable] Search places by name or coordinates."""
|
|
449
449
|
data = api_get("/v1/instagram/search/places", {"query": query, "lat": lat, "lng": lng})
|
|
450
450
|
render(data, fmt, fields)
|
|
451
451
|
|
|
@@ -467,7 +467,7 @@ def search_reels(
|
|
|
467
467
|
fmt: str = FMT,
|
|
468
468
|
fields: str | None = FIELDS,
|
|
469
469
|
) -> None:
|
|
470
|
-
"""Search reels."""
|
|
470
|
+
"""[temporarily unavailable] Search reels."""
|
|
471
471
|
data = api_get("/v1/instagram/search/reels", {"query": query})
|
|
472
472
|
render(data, fmt, fields)
|
|
473
473
|
|
|
@@ -478,7 +478,7 @@ def search_music(
|
|
|
478
478
|
fmt: str = FMT,
|
|
479
479
|
fields: str | None = FIELDS,
|
|
480
480
|
) -> None:
|
|
481
|
-
"""Search music/audio."""
|
|
481
|
+
"""[temporarily unavailable] Search music/audio."""
|
|
482
482
|
data = api_get("/v1/instagram/search/music", {"query": query})
|
|
483
483
|
render(data, fmt, fields)
|
|
484
484
|
|
|
@@ -489,7 +489,7 @@ def autocomplete(
|
|
|
489
489
|
fmt: str = FMT,
|
|
490
490
|
fields: str | None = FIELDS,
|
|
491
491
|
) -> None:
|
|
492
|
-
"""Get search autocomplete suggestions."""
|
|
492
|
+
"""[temporarily unavailable] Get search autocomplete suggestions."""
|
|
493
493
|
data = api_get("/v1/instagram/search/autocomplete", {"query": query})
|
|
494
494
|
render(data, fmt, fields)
|
|
495
495
|
|
|
@@ -503,7 +503,7 @@ def audio(
|
|
|
503
503
|
fmt: str = FMT,
|
|
504
504
|
fields: str | None = FIELDS,
|
|
505
505
|
) -> None:
|
|
506
|
-
"""Get audio track metadata."""
|
|
506
|
+
"""[temporarily unavailable] Get audio track metadata."""
|
|
507
507
|
data = api_get(f"/v1/instagram/audio/{audio_id}")
|
|
508
508
|
render(data, fmt, fields)
|
|
509
509
|
|
|
@@ -516,7 +516,7 @@ def audio_media(
|
|
|
516
516
|
fmt: str = FMT,
|
|
517
517
|
fields: str | None = FIELDS,
|
|
518
518
|
) -> None:
|
|
519
|
-
"""Get media using an audio track."""
|
|
519
|
+
"""[temporarily unavailable] Get media using an audio track."""
|
|
520
520
|
data = api_get(f"/v1/instagram/audio/{audio_id}/media", {"amount": amount, "cursor": cursor})
|
|
521
521
|
render(data, fmt, fields)
|
|
522
522
|
|
|
@@ -527,6 +527,6 @@ def trending_audio(
|
|
|
527
527
|
fmt: str = FMT,
|
|
528
528
|
fields: str | None = FIELDS,
|
|
529
529
|
) -> None:
|
|
530
|
-
"""Get trending audio."""
|
|
530
|
+
"""[temporarily unavailable] Get trending audio."""
|
|
531
531
|
data = api_get("/v1/instagram/audio/trending", {"product": product})
|
|
532
532
|
render(data, fmt, fields)
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
"""Walmart marketplace commands. US-only — no market/domain option."""
|
|
2
|
+
|
|
3
|
+
import typer
|
|
4
|
+
|
|
5
|
+
from scrapebadger_cli.client import api_get
|
|
6
|
+
from scrapebadger_cli.output import render
|
|
7
|
+
|
|
8
|
+
app = typer.Typer(no_args_is_help=True)
|
|
9
|
+
|
|
10
|
+
FMT = typer.Option("json", "--output", "-o", help="Output format: json, csv, table, markdown")
|
|
11
|
+
FIELDS = typer.Option(None, "--fields", "-f", help="Comma-separated fields to include")
|
|
12
|
+
|
|
13
|
+
SORT_HELP = "best_match, best_seller, price_low, price_high, rating_high, new"
|
|
14
|
+
|
|
15
|
+
_ITEM_COLS = "us_item_id,name,price,rating,review_count,seller_name"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _show(
|
|
19
|
+
data: object, fmt: str, fields: str | None, table_cols: str, key: str | None = None
|
|
20
|
+
) -> None:
|
|
21
|
+
"""Render, defaulting the columns for the human formats only.
|
|
22
|
+
|
|
23
|
+
json and csv keep the whole envelope so a machine-readable export never
|
|
24
|
+
silently drops fields; table/markdown drill to `key` (the response's own
|
|
25
|
+
row list, when `output.render` cannot find it) and default to `table_cols`.
|
|
26
|
+
"""
|
|
27
|
+
if fmt in ("table", "markdown"):
|
|
28
|
+
if key and isinstance(data, dict):
|
|
29
|
+
data = data.get(key, data)
|
|
30
|
+
fields = fields or table_cols
|
|
31
|
+
render(data, fmt, fields)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@app.command()
|
|
35
|
+
def search(
|
|
36
|
+
query: str = typer.Argument(..., help="Search keywords, e.g. 'laptop'"),
|
|
37
|
+
page: int = typer.Option(1, "--page", "-p", help="1-10"),
|
|
38
|
+
sort: str | None = typer.Option(None, "--sort", "-s", help=SORT_HELP),
|
|
39
|
+
min_price: float | None = typer.Option(None, "--min-price"),
|
|
40
|
+
max_price: float | None = typer.Option(None, "--max-price"),
|
|
41
|
+
facet: str | None = typer.Option(None, "--facet", help="Facet filter, e.g. 'brand:HP'"),
|
|
42
|
+
fmt: str = FMT,
|
|
43
|
+
fields: str | None = FIELDS,
|
|
44
|
+
) -> None:
|
|
45
|
+
"""Search walmart.com products by keyword."""
|
|
46
|
+
data = api_get(
|
|
47
|
+
"/v1/walmart/search",
|
|
48
|
+
{
|
|
49
|
+
"query": query,
|
|
50
|
+
"page": page,
|
|
51
|
+
"sort": sort,
|
|
52
|
+
"min_price": min_price,
|
|
53
|
+
"max_price": max_price,
|
|
54
|
+
"facet": facet,
|
|
55
|
+
},
|
|
56
|
+
)
|
|
57
|
+
_show(data, fmt, fields, _ITEM_COLS)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@app.command()
|
|
61
|
+
def category(
|
|
62
|
+
path: str = typer.Argument(..., help="Category browse path, e.g. 'electronics/3944'"),
|
|
63
|
+
page: int = typer.Option(1, "--page", "-p", help="1-11"),
|
|
64
|
+
min_price: float | None = typer.Option(None, "--min-price"),
|
|
65
|
+
max_price: float | None = typer.Option(None, "--max-price"),
|
|
66
|
+
facet: str | None = typer.Option(None, "--facet", help="Facet filter, e.g. 'brand:HP'"),
|
|
67
|
+
fmt: str = FMT,
|
|
68
|
+
fields: str | None = FIELDS,
|
|
69
|
+
) -> None:
|
|
70
|
+
"""Browse a walmart.com category.
|
|
71
|
+
|
|
72
|
+
No --sort: Walmart's browse surface ignores it. Use `search` for ordering.
|
|
73
|
+
"""
|
|
74
|
+
data = api_get(
|
|
75
|
+
"/v1/walmart/category",
|
|
76
|
+
{
|
|
77
|
+
"path": path,
|
|
78
|
+
"page": page,
|
|
79
|
+
"min_price": min_price,
|
|
80
|
+
"max_price": max_price,
|
|
81
|
+
"facet": facet,
|
|
82
|
+
},
|
|
83
|
+
)
|
|
84
|
+
_show(data, fmt, fields, _ITEM_COLS)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@app.command()
|
|
88
|
+
def deals(
|
|
89
|
+
page: int = typer.Option(1, "--page", "-p", help="1-11"),
|
|
90
|
+
min_price: float | None = typer.Option(None, "--min-price"),
|
|
91
|
+
max_price: float | None = typer.Option(None, "--max-price"),
|
|
92
|
+
fmt: str = FMT,
|
|
93
|
+
fields: str | None = FIELDS,
|
|
94
|
+
) -> None:
|
|
95
|
+
"""List current walmart.com deals (rollbacks and clearance)."""
|
|
96
|
+
data = api_get(
|
|
97
|
+
"/v1/walmart/deals",
|
|
98
|
+
{"page": page, "min_price": min_price, "max_price": max_price},
|
|
99
|
+
)
|
|
100
|
+
_show(data, fmt, fields, _ITEM_COLS)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
@app.command()
|
|
104
|
+
def autocomplete(
|
|
105
|
+
query: str = typer.Argument(..., help="Partial search term, e.g. 'lapt'"),
|
|
106
|
+
fmt: str = FMT,
|
|
107
|
+
fields: str | None = FIELDS,
|
|
108
|
+
) -> None:
|
|
109
|
+
"""Get walmart.com search autocomplete suggestions."""
|
|
110
|
+
data = api_get("/v1/walmart/autocomplete", {"query": query})
|
|
111
|
+
_show(data, fmt, fields, "query,type,department_name,url", key="suggestions")
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
@app.command()
|
|
115
|
+
def product(
|
|
116
|
+
item_id: str = typer.Argument(..., help="Walmart US item ID"),
|
|
117
|
+
fmt: str = FMT,
|
|
118
|
+
fields: str | None = FIELDS,
|
|
119
|
+
) -> None:
|
|
120
|
+
"""Get a full Walmart product detail page (PDP)."""
|
|
121
|
+
data = api_get(f"/v1/walmart/products/{item_id}")
|
|
122
|
+
_show(data, fmt, fields, "us_item_id,name,brand,price,rating,review_count,availability_status")
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
@app.command()
|
|
126
|
+
def reviews(
|
|
127
|
+
item_id: str = typer.Argument(..., help="Walmart US item ID"),
|
|
128
|
+
page: int = typer.Option(1, "--page", "-p", help="1-100"),
|
|
129
|
+
sort: str | None = typer.Option(
|
|
130
|
+
None,
|
|
131
|
+
"--sort",
|
|
132
|
+
"-s",
|
|
133
|
+
help="relevancy, submission-desc, submission-asc, rating-desc, rating-asc, helpful",
|
|
134
|
+
),
|
|
135
|
+
fmt: str = FMT,
|
|
136
|
+
fields: str | None = FIELDS,
|
|
137
|
+
) -> None:
|
|
138
|
+
"""Get a Walmart product's reviews and rating distribution."""
|
|
139
|
+
data = api_get(f"/v1/walmart/products/{item_id}/reviews", {"page": page, "sort": sort})
|
|
140
|
+
_show(
|
|
141
|
+
data,
|
|
142
|
+
fmt,
|
|
143
|
+
fields,
|
|
144
|
+
"review_id,rating,title,author,submitted_at,verified_purchase",
|
|
145
|
+
key="reviews",
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
@app.command()
|
|
150
|
+
def seller(
|
|
151
|
+
seller_id: str = typer.Argument(..., help="Numeric catalog seller ID, e.g. 101040442"),
|
|
152
|
+
fmt: str = FMT,
|
|
153
|
+
fields: str | None = FIELDS,
|
|
154
|
+
) -> None:
|
|
155
|
+
"""Get a Walmart marketplace seller profile."""
|
|
156
|
+
data = api_get(f"/v1/walmart/sellers/{seller_id}")
|
|
157
|
+
_show(
|
|
158
|
+
data,
|
|
159
|
+
fmt,
|
|
160
|
+
fields,
|
|
161
|
+
"seller_id,name,seller_type,rating,review_count,city,state",
|
|
162
|
+
key="seller",
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
@app.command(name="seller-products")
|
|
167
|
+
def seller_products(
|
|
168
|
+
seller_id: str = typer.Argument(..., help="Numeric catalog seller ID, e.g. 101040442"),
|
|
169
|
+
query: str = typer.Option(..., "--query", "-q", help="Search text (required)"),
|
|
170
|
+
page: int = typer.Option(1, "--page", "-p", help="1-10"),
|
|
171
|
+
sort: str | None = typer.Option(None, "--sort", "-s", help=SORT_HELP),
|
|
172
|
+
fmt: str = FMT,
|
|
173
|
+
fields: str | None = FIELDS,
|
|
174
|
+
) -> None:
|
|
175
|
+
"""Search a Walmart seller's catalogue.
|
|
176
|
+
|
|
177
|
+
--query is required: the storefront renders client-side, so a seller's
|
|
178
|
+
listings are only reachable through search filtered to that seller.
|
|
179
|
+
"""
|
|
180
|
+
data = api_get(
|
|
181
|
+
f"/v1/walmart/sellers/{seller_id}/products",
|
|
182
|
+
{"query": query, "page": page, "sort": sort},
|
|
183
|
+
)
|
|
184
|
+
_show(data, fmt, fields, _ITEM_COLS)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
@app.command()
|
|
188
|
+
def store(
|
|
189
|
+
store_id: str = typer.Argument(..., help="Walmart store number, e.g. 100"),
|
|
190
|
+
fmt: str = FMT,
|
|
191
|
+
fields: str | None = FIELDS,
|
|
192
|
+
) -> None:
|
|
193
|
+
"""Get a Walmart store: address, hours, services and nearby stores."""
|
|
194
|
+
data = api_get(f"/v1/walmart/stores/{store_id}")
|
|
195
|
+
_show(
|
|
196
|
+
data,
|
|
197
|
+
fmt,
|
|
198
|
+
fields,
|
|
199
|
+
"store_id,name,city,state,postal_code,phone,open_24_hours",
|
|
200
|
+
key="store",
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
@app.command()
|
|
205
|
+
def markets(
|
|
206
|
+
fmt: str = FMT,
|
|
207
|
+
fields: str | None = FIELDS,
|
|
208
|
+
) -> None:
|
|
209
|
+
"""List supported Walmart markets (US only)."""
|
|
210
|
+
data = api_get("/v1/walmart/markets")
|
|
211
|
+
_show(data, fmt, fields, "code,name,domain,currency,language", key="markets")
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"""Yandex search commands. Default market tr (yandex.com.tr) clears anti-bot
|
|
2
|
+
most reliably. Reverse image search is the standout feature."""
|
|
3
|
+
|
|
4
|
+
import typer
|
|
5
|
+
|
|
6
|
+
from scrapebadger_cli.client import api_get
|
|
7
|
+
from scrapebadger_cli.output import render
|
|
8
|
+
|
|
9
|
+
app = typer.Typer(no_args_is_help=True)
|
|
10
|
+
|
|
11
|
+
FMT = typer.Option("json", "--output", "-o", help="Output format: json, csv, table, markdown")
|
|
12
|
+
FIELDS = typer.Option(None, "--fields", "-f", help="Comma-separated fields to include")
|
|
13
|
+
|
|
14
|
+
DOMAIN = typer.Option("tr", "--domain", "-d", help="Market: tr, com, ru, by, kz, uz")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _show(
|
|
18
|
+
data: object, fmt: str, fields: str | None, table_cols: str, key: str | None = None
|
|
19
|
+
) -> None:
|
|
20
|
+
"""Render, defaulting the columns for the human formats only.
|
|
21
|
+
|
|
22
|
+
json and csv keep the whole envelope so a machine-readable export never
|
|
23
|
+
silently drops fields; table/markdown drill to `key` (the response's own
|
|
24
|
+
row list, when `output.render` cannot find it) and default to `table_cols`.
|
|
25
|
+
"""
|
|
26
|
+
if fmt in ("table", "markdown"):
|
|
27
|
+
if key and isinstance(data, dict):
|
|
28
|
+
data = data.get(key, data)
|
|
29
|
+
fields = fields or table_cols
|
|
30
|
+
render(data, fmt, fields)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@app.command()
|
|
34
|
+
def search(
|
|
35
|
+
query: str = typer.Argument(..., help="Search keywords, e.g. 'kofe'"),
|
|
36
|
+
domain: str = DOMAIN,
|
|
37
|
+
page: int = typer.Option(1, "--page", "-p", help="1-25"),
|
|
38
|
+
lr: int | None = typer.Option(None, "--lr", help="Yandex region id (int)"),
|
|
39
|
+
lang: str | None = typer.Option(None, "--lang", help="ru, en, tr, be, kk, uk"),
|
|
40
|
+
fmt: str = FMT,
|
|
41
|
+
fields: str | None = FIELDS,
|
|
42
|
+
) -> None:
|
|
43
|
+
"""Search Yandex web results by keyword."""
|
|
44
|
+
data = api_get(
|
|
45
|
+
"/v1/yandex/search",
|
|
46
|
+
{"query": query, "domain": domain, "page": page, "lr": lr, "lang": lang},
|
|
47
|
+
)
|
|
48
|
+
_show(
|
|
49
|
+
data,
|
|
50
|
+
fmt,
|
|
51
|
+
fields,
|
|
52
|
+
"position,title,url,displayed_url,domain,snippet",
|
|
53
|
+
key="organic_results",
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@app.command()
|
|
58
|
+
def images(
|
|
59
|
+
query: str = typer.Argument(..., help="Search keywords, e.g. 'sunset'"),
|
|
60
|
+
domain: str = DOMAIN,
|
|
61
|
+
page: int = typer.Option(1, "--page", "-p", help="1-25"),
|
|
62
|
+
fmt: str = FMT,
|
|
63
|
+
fields: str | None = FIELDS,
|
|
64
|
+
) -> None:
|
|
65
|
+
"""Search Yandex Images by text query."""
|
|
66
|
+
data = api_get(
|
|
67
|
+
"/v1/yandex/images/search",
|
|
68
|
+
{"query": query, "domain": domain, "page": page},
|
|
69
|
+
)
|
|
70
|
+
_show(
|
|
71
|
+
data,
|
|
72
|
+
fmt,
|
|
73
|
+
fields,
|
|
74
|
+
"position,title,source,source_url,thumbnail",
|
|
75
|
+
key="results",
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@app.command()
|
|
80
|
+
def reverse(
|
|
81
|
+
image_url: str = typer.Argument(..., help="Public URL of the image to search by"),
|
|
82
|
+
domain: str = DOMAIN,
|
|
83
|
+
fmt: str = FMT,
|
|
84
|
+
fields: str | None = FIELDS,
|
|
85
|
+
) -> None:
|
|
86
|
+
"""Reverse image search on Yandex (CBIR) — find where an image appears
|
|
87
|
+
online, plus visually similar images, tags and other sizes."""
|
|
88
|
+
data = api_get(
|
|
89
|
+
"/v1/yandex/images/reverse",
|
|
90
|
+
{"image_url": image_url, "domain": domain},
|
|
91
|
+
)
|
|
92
|
+
_show(data, fmt, fields, "title,description,url,domain", key="sites")
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
@app.command()
|
|
96
|
+
def markets(
|
|
97
|
+
fmt: str = FMT,
|
|
98
|
+
fields: str | None = FIELDS,
|
|
99
|
+
) -> None:
|
|
100
|
+
"""List supported Yandex markets (tr, com, ru, by, kz, uz)."""
|
|
101
|
+
data = api_get("/v1/yandex/markets")
|
|
102
|
+
_show(data, fmt, fields, "code,name,domain,country_code,default_lr,lang", key="markets")
|
|
@@ -6,8 +6,11 @@ from scrapebadger_cli.commands import (
|
|
|
6
6
|
amazon,
|
|
7
7
|
apartments,
|
|
8
8
|
auth,
|
|
9
|
+
baidu,
|
|
10
|
+
bing,
|
|
9
11
|
chatgpt,
|
|
10
12
|
depop,
|
|
13
|
+
duckduckgo,
|
|
11
14
|
ebay,
|
|
12
15
|
facebook,
|
|
13
16
|
google,
|
|
@@ -24,7 +27,9 @@ from scrapebadger_cli.commands import (
|
|
|
24
27
|
tiktok,
|
|
25
28
|
twitter,
|
|
26
29
|
vinted,
|
|
30
|
+
walmart,
|
|
27
31
|
web,
|
|
32
|
+
yandex,
|
|
28
33
|
youtube,
|
|
29
34
|
zillow,
|
|
30
35
|
)
|
|
@@ -42,6 +47,7 @@ app.add_typer(stream.app, name="stream", help="Real-time streaming commands")
|
|
|
42
47
|
app.add_typer(vinted.app, name="vinted", help="Vinted marketplace commands")
|
|
43
48
|
app.add_typer(amazon.app, name="amazon", help="Amazon marketplace commands")
|
|
44
49
|
app.add_typer(ebay.app, name="ebay", help="eBay marketplace commands")
|
|
50
|
+
app.add_typer(walmart.app, name="walmart", help="Walmart marketplace commands")
|
|
45
51
|
app.add_typer(idealista.app, name="idealista", help="Idealista real-estate commands")
|
|
46
52
|
app.add_typer(immobiliare.app, name="immobiliare", help="Immobiliare.it real-estate commands")
|
|
47
53
|
app.add_typer(leboncoin.app, name="leboncoin", help="Leboncoin classifieds commands")
|
|
@@ -50,6 +56,10 @@ app.add_typer(youtube.app, name="youtube", help="YouTube commands")
|
|
|
50
56
|
app.add_typer(zillow.app, name="zillow", help="Zillow real-estate commands")
|
|
51
57
|
app.add_typer(web.app, name="web", help="Web scraping commands")
|
|
52
58
|
app.add_typer(google.app, name="google", help="Google scraper commands")
|
|
59
|
+
app.add_typer(bing.app, name="bing", help="Bing search engine commands")
|
|
60
|
+
app.add_typer(baidu.app, name="baidu", help="Baidu search engine commands")
|
|
61
|
+
app.add_typer(duckduckgo.app, name="duckduckgo", help="DuckDuckGo search commands")
|
|
62
|
+
app.add_typer(yandex.app, name="yandex", help="Yandex search commands")
|
|
53
63
|
app.add_typer(realtor.app, name="realtor", help="Realtor real-estate commands")
|
|
54
64
|
app.add_typer(loopnet.app, name="loopnet", help="LoopNet commercial real estate commands")
|
|
55
65
|
app.add_typer(depop.app, name="depop", help="Depop marketplace commands")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{scrapebadger_cli-0.7.0 → scrapebadger_cli-0.9.0}/src/scrapebadger_cli/commands/apartments.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{scrapebadger_cli-0.7.0 → scrapebadger_cli-0.9.0}/src/scrapebadger_cli/commands/idealista.py
RENAMED
|
File without changes
|
{scrapebadger_cli-0.7.0 → scrapebadger_cli-0.9.0}/src/scrapebadger_cli/commands/immobiliare.py
RENAMED
|
File without changes
|
{scrapebadger_cli-0.7.0 → scrapebadger_cli-0.9.0}/src/scrapebadger_cli/commands/leboncoin.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|