flru-parser 0.3.0__py3-none-any.whl
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.
- flru/__init__.py +138 -0
- flru/batch.py +28 -0
- flru/canary.py +33 -0
- flru/client.py +771 -0
- flru/config.py +114 -0
- flru/cookies.py +15 -0
- flru/easy.py +514 -0
- flru/exceptions.py +60 -0
- flru/filters.py +38 -0
- flru/integrations/__init__.py +1 -0
- flru/integrations/opentelemetry.py +29 -0
- flru/integrations/prometheus.py +35 -0
- flru/models.py +297 -0
- flru/observability.py +118 -0
- flru/parsers/__init__.py +13 -0
- flru/parsers/common.py +227 -0
- flru/parsers/freelancers.py +97 -0
- flru/parsers/projects.py +281 -0
- flru/parsers/users.py +193 -0
- flru/proxy.py +87 -0
- flru/py.typed +0 -0
- flru/resilience.py +157 -0
- flru/robots.py +45 -0
- flru/security.py +41 -0
- flru/state.py +324 -0
- flru/sync.py +218 -0
- flru/transport.py +362 -0
- flru_parser-0.3.0.dist-info/METADATA +421 -0
- flru_parser-0.3.0.dist-info/RECORD +33 -0
- flru_parser-0.3.0.dist-info/WHEEL +5 -0
- flru_parser-0.3.0.dist-info/entry_points.txt +2 -0
- flru_parser-0.3.0.dist-info/licenses/LICENSE +21 -0
- flru_parser-0.3.0.dist-info/top_level.txt +1 -0
flru/__init__.py
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
from .batch import BatchError, BatchResult
|
|
2
|
+
from .client import FLClient
|
|
3
|
+
from .config import (
|
|
4
|
+
CircuitBreakerConfig,
|
|
5
|
+
ClientConfig,
|
|
6
|
+
ProxyConfig,
|
|
7
|
+
RateLimitConfig,
|
|
8
|
+
RetryConfig,
|
|
9
|
+
TimeoutConfig,
|
|
10
|
+
)
|
|
11
|
+
from .easy import (
|
|
12
|
+
Client,
|
|
13
|
+
Pages,
|
|
14
|
+
fetch_freelancers,
|
|
15
|
+
fetch_project,
|
|
16
|
+
fetch_projects,
|
|
17
|
+
fetch_user,
|
|
18
|
+
)
|
|
19
|
+
from .exceptions import (
|
|
20
|
+
AuthenticationRequired,
|
|
21
|
+
BlockedError,
|
|
22
|
+
CircuitOpenError,
|
|
23
|
+
ConfigurationError,
|
|
24
|
+
EmptyPageError,
|
|
25
|
+
FLRUError,
|
|
26
|
+
HTTPStatusError,
|
|
27
|
+
ParseError,
|
|
28
|
+
RateLimitedError,
|
|
29
|
+
RobotsDeniedError,
|
|
30
|
+
SecurityError,
|
|
31
|
+
SelectorDriftError,
|
|
32
|
+
TransportError,
|
|
33
|
+
UnexpectedPageError,
|
|
34
|
+
)
|
|
35
|
+
from .filters import ProjectFilters, ProjectType
|
|
36
|
+
from .models import (
|
|
37
|
+
Attachment,
|
|
38
|
+
Category,
|
|
39
|
+
CrawlCheckpoint,
|
|
40
|
+
FreelancerPage,
|
|
41
|
+
FreelancerSummary,
|
|
42
|
+
Image,
|
|
43
|
+
Link,
|
|
44
|
+
Money,
|
|
45
|
+
PageData,
|
|
46
|
+
ParseDiagnostics,
|
|
47
|
+
PortfolioItem,
|
|
48
|
+
ProjectDetail,
|
|
49
|
+
ProjectKind,
|
|
50
|
+
ProjectPage,
|
|
51
|
+
ProjectRecord,
|
|
52
|
+
ProjectStatus,
|
|
53
|
+
ProjectSummary,
|
|
54
|
+
RequestMetrics,
|
|
55
|
+
Review,
|
|
56
|
+
SourceInfo,
|
|
57
|
+
UserProfile,
|
|
58
|
+
UserSummary,
|
|
59
|
+
)
|
|
60
|
+
from .observability import RequestEvent, StructuredLogHandler
|
|
61
|
+
from .state import (
|
|
62
|
+
CrawlStateStore,
|
|
63
|
+
MemoryStateStore,
|
|
64
|
+
PostgresStateStore,
|
|
65
|
+
RedisStateStore,
|
|
66
|
+
SQLiteStateStore,
|
|
67
|
+
project_content_hash,
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
__version__ = "0.3.0"
|
|
71
|
+
|
|
72
|
+
AsyncClient = Client
|
|
73
|
+
|
|
74
|
+
__all__ = [
|
|
75
|
+
"__version__",
|
|
76
|
+
"AsyncClient",
|
|
77
|
+
"Attachment",
|
|
78
|
+
"AuthenticationRequired",
|
|
79
|
+
"BatchError",
|
|
80
|
+
"BatchResult",
|
|
81
|
+
"BlockedError",
|
|
82
|
+
"Category",
|
|
83
|
+
"CircuitBreakerConfig",
|
|
84
|
+
"CircuitOpenError",
|
|
85
|
+
"Client",
|
|
86
|
+
"ClientConfig",
|
|
87
|
+
"ConfigurationError",
|
|
88
|
+
"CrawlCheckpoint",
|
|
89
|
+
"CrawlStateStore",
|
|
90
|
+
"EmptyPageError",
|
|
91
|
+
"FLClient",
|
|
92
|
+
"FLRUError",
|
|
93
|
+
"FreelancerPage",
|
|
94
|
+
"FreelancerSummary",
|
|
95
|
+
"HTTPStatusError",
|
|
96
|
+
"Image",
|
|
97
|
+
"Link",
|
|
98
|
+
"MemoryStateStore",
|
|
99
|
+
"Money",
|
|
100
|
+
"PageData",
|
|
101
|
+
"Pages",
|
|
102
|
+
"ParseDiagnostics",
|
|
103
|
+
"ParseError",
|
|
104
|
+
"PortfolioItem",
|
|
105
|
+
"PostgresStateStore",
|
|
106
|
+
"ProjectDetail",
|
|
107
|
+
"ProjectFilters",
|
|
108
|
+
"ProjectKind",
|
|
109
|
+
"ProjectPage",
|
|
110
|
+
"ProjectRecord",
|
|
111
|
+
"ProjectStatus",
|
|
112
|
+
"ProjectSummary",
|
|
113
|
+
"ProjectType",
|
|
114
|
+
"ProxyConfig",
|
|
115
|
+
"RateLimitConfig",
|
|
116
|
+
"RateLimitedError",
|
|
117
|
+
"RedisStateStore",
|
|
118
|
+
"RequestEvent",
|
|
119
|
+
"RequestMetrics",
|
|
120
|
+
"RetryConfig",
|
|
121
|
+
"Review",
|
|
122
|
+
"RobotsDeniedError",
|
|
123
|
+
"SQLiteStateStore",
|
|
124
|
+
"SecurityError",
|
|
125
|
+
"SelectorDriftError",
|
|
126
|
+
"SourceInfo",
|
|
127
|
+
"StructuredLogHandler",
|
|
128
|
+
"TimeoutConfig",
|
|
129
|
+
"TransportError",
|
|
130
|
+
"UnexpectedPageError",
|
|
131
|
+
"UserProfile",
|
|
132
|
+
"UserSummary",
|
|
133
|
+
"project_content_hash",
|
|
134
|
+
"fetch_freelancers",
|
|
135
|
+
"fetch_project",
|
|
136
|
+
"fetch_projects",
|
|
137
|
+
"fetch_user",
|
|
138
|
+
]
|
flru/batch.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from typing import Generic, TypeVar
|
|
5
|
+
|
|
6
|
+
T = TypeVar("T")
|
|
7
|
+
K = TypeVar("K")
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass(slots=True, frozen=True)
|
|
11
|
+
class BatchError(Generic[K]):
|
|
12
|
+
key: K
|
|
13
|
+
error: Exception
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass(slots=True)
|
|
17
|
+
class BatchResult(Generic[K, T]):
|
|
18
|
+
successful: list[T] = field(default_factory=list)
|
|
19
|
+
failed: list[BatchError[K]] = field(default_factory=list)
|
|
20
|
+
|
|
21
|
+
@property
|
|
22
|
+
def ok(self) -> bool:
|
|
23
|
+
return not self.failed
|
|
24
|
+
|
|
25
|
+
def raise_for_errors(self) -> None:
|
|
26
|
+
if self.failed:
|
|
27
|
+
first = self.failed[0]
|
|
28
|
+
raise first.error
|
flru/canary.py
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import asyncio
|
|
5
|
+
import json
|
|
6
|
+
|
|
7
|
+
from .client import FLClient
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
async def run_canary() -> dict[str, object]:
|
|
11
|
+
async with FLClient() as client:
|
|
12
|
+
page = await client.get_projects_page(1)
|
|
13
|
+
first = page.items[0]
|
|
14
|
+
return {
|
|
15
|
+
"ok": True,
|
|
16
|
+
"items": len(page.items),
|
|
17
|
+
"first_id": first.id,
|
|
18
|
+
"first_title": first.title,
|
|
19
|
+
"fingerprint": page.diagnostics.page_fingerprint,
|
|
20
|
+
"confidence": page.diagnostics.confidence,
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def main() -> None:
|
|
25
|
+
parser = argparse.ArgumentParser(description="Run a read-only FL.ru parser canary")
|
|
26
|
+
parser.add_argument("--pretty", action="store_true")
|
|
27
|
+
args = parser.parse_args()
|
|
28
|
+
result = asyncio.run(run_canary())
|
|
29
|
+
print(json.dumps(result, ensure_ascii=False, indent=2 if args.pretty else None))
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
if __name__ == "__main__":
|
|
33
|
+
main()
|