reycrawler 1.0.7__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.
@@ -0,0 +1,10 @@
1
+ # Python-generated files
2
+ __pycache__/
3
+ *.py[oc]
4
+ build/
5
+ dist/
6
+ wheels/
7
+ *.egg-info
8
+
9
+ # Virtual environments
10
+ .venv
@@ -0,0 +1 @@
1
+ 3.13
@@ -0,0 +1,7 @@
1
+ Copyright 2025 ReyXBo
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,33 @@
1
+ Metadata-Version: 2.4
2
+ Name: reycrawler
3
+ Version: 1.0.7
4
+ Summary: Web crawler method set.
5
+ Project-URL: Homepage, https://www.reyxbo.com
6
+ Project-URL: Repository, https://github.com/reyxbo/reycrawler-py.git
7
+ Author-email: Rey <reyxbo@163.com>
8
+ License: Copyright 2025 ReyXBo
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
15
+ License-File: LICENSE
16
+ Keywords: crawl Web,crawler,rey,reyxbo
17
+ Requires-Python: >=3.12
18
+ Requires-Dist: bs4
19
+ Requires-Dist: fake-useragent
20
+ Requires-Dist: reydb
21
+ Requires-Dist: reykit
22
+ Requires-Dist: selenium
23
+ Description-Content-Type: text/markdown
24
+
25
+ # reycrawler
26
+
27
+ > Web crawler method set.
28
+
29
+ ## Install
30
+
31
+ ```
32
+ pip install reyfetch
33
+ ```
@@ -0,0 +1,9 @@
1
+ # reycrawler
2
+
3
+ > Web crawler method set.
4
+
5
+ ## Install
6
+
7
+ ```
8
+ pip install reyfetch
9
+ ```
@@ -0,0 +1,26 @@
1
+ [project]
2
+ name = "reycrawler"
3
+ version = "1.0.7"
4
+ description = "Web crawler method set."
5
+ readme = "README.md"
6
+ requires-python = ">=3.12"
7
+ license = {file = "LICENSE"}
8
+ keywords = ["rey", "reyxbo", "crawler", "crawl Web"]
9
+ authors = [
10
+ {name = "Rey", email = "reyxbo@163.com"}
11
+ ]
12
+ dependencies = [
13
+ "bs4",
14
+ "fake_useragent",
15
+ "reydb",
16
+ "reykit",
17
+ "selenium"
18
+ ]
19
+
20
+ [project.urls]
21
+ Homepage = "https://www.reyxbo.com"
22
+ Repository = "https://github.com/reyxbo/reycrawler-py.git"
23
+
24
+ [build-system]
25
+ requires = ["hatchling"]
26
+ build-backend = "hatchling.build"
@@ -0,0 +1,18 @@
1
+ # !/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+
4
+ """
5
+ @Time : 2023-02-19
6
+ @Author : Rey
7
+ @Contact : reyxbo@163.com
8
+ @Explain : Web crawler method set.
9
+
10
+ Modules
11
+ -------
12
+ rall : All import methods.
13
+ rbase : Base methods.
14
+ rbrowser : Browser methods.
15
+ rdouban : Crawl Douban website methods.
16
+ rdouban : Crawl Douban website methods.
17
+ rdouban : Crawl Douban website methods.
18
+ """
@@ -0,0 +1,20 @@
1
+ # !/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+
4
+ """
5
+ @Time : 2022-12-08
6
+ @Author : Rey
7
+ @Contact : reyxbo@163.com
8
+ @Explain : All import methods.
9
+ """
10
+
11
+ # ruff: noqa: F403
12
+
13
+ from .rbaidu import *
14
+ from .rbase import *
15
+ from .rbrowser import *
16
+ from .rdouban import *
17
+ from .rother import *
18
+ from .rsina import *
19
+ from .rtoutiao import *
20
+ from .rweibo import *
@@ -0,0 +1,116 @@
1
+ # !/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+
4
+ """
5
+ @Time : 2024-01-10
6
+ @Author : Rey
7
+ @Contact : reyxbo@163.com
8
+ @Explain : Baidu website crawling methods.
9
+ """
10
+
11
+ from json import loads as json_loads
12
+ from reykit.rnet import request
13
+ from reykit.rre import search
14
+ from reykit.rtime import now
15
+
16
+ __all__ = (
17
+ 'crawl_baidu_calendar',
18
+ )
19
+
20
+ def crawl_baidu_calendar(
21
+ year: int | None = None,
22
+ month: int | None = None
23
+ ) -> list[dict]:
24
+ """
25
+ Crawl Baidu website calendar table.
26
+
27
+ Parameters
28
+ ----------
29
+ year : Given year.
30
+ - `None`: Now year.
31
+ month : Given month.
32
+ - `None`: Now month.
33
+
34
+ Returns
35
+ -------
36
+ Calendar table.
37
+ """
38
+
39
+ # Get parameter.
40
+ now_date = now('date')
41
+ year = year or now_date.year
42
+ month = month or now_date.month
43
+ if month == 12:
44
+ month = 1
45
+ else:
46
+ month += 1
47
+ url = 'https://opendata.baidu.com/data/inner'
48
+ query = '%s年%s月' % (year, month)
49
+ params = {
50
+ 'tn': 'reserved_all_res_tn',
51
+ 'type': 'json',
52
+ 'resource_id': '52109',
53
+ 'query': query,
54
+ 'apiType': 'yearMonthData',
55
+ 'cb': 'jsonp_1706670926975_94318'
56
+ }
57
+
58
+ # Request.
59
+ response = request(url, params)
60
+
61
+ # Extract.
62
+ pattern = '{.+}'
63
+ text = search(pattern, response.text)
64
+ data: dict = json_loads(text)
65
+ table: list[dict] = data['Result'][0]['DisplayData']['resultData']['tplData']['data']['almanac']
66
+
67
+ # Convert.
68
+ week_dict = {
69
+ '一': 0,
70
+ '二': 1,
71
+ '三': 2,
72
+ '四': 3,
73
+ '五': 4,
74
+ '六': 5,
75
+ '日': 6
76
+ }
77
+ table = [
78
+ {
79
+ 'year': int(row['year']),
80
+ 'month': int(row['month']),
81
+ 'day': int(row['day']),
82
+ 'week': week_dict[row['cnDay']],
83
+ 'work': row.get('status'),
84
+ 'festival': [
85
+ {
86
+ 'name': info['name'],
87
+ 'url': info.get('baikeUrl')
88
+ }
89
+ for info in row.get('festivalInfoList', [])
90
+ ],
91
+ 'animal': row['animal'],
92
+ 'lunar_year': int(row['lunarYear']),
93
+ 'lunar_month': int(row['lunarMonth']),
94
+ 'lunar_day': int(row['lunarDate']),
95
+ 'gz_year': row['gzYear'],
96
+ 'gz_month': row['gzMonth'],
97
+ 'gz_day': row['gzDate'],
98
+ 'suit': row['suit'].split('.'),
99
+ 'avoid': row['avoid'].split('.'),
100
+ 'url': row['yjJumpUrl']
101
+ }
102
+ for row in table
103
+ ]
104
+ for row in table:
105
+ week = row['week']
106
+ work = row['work']
107
+ match work:
108
+ case None:
109
+ is_work_day = week not in (5, 6)
110
+ case '1':
111
+ is_work_day = False
112
+ case '2':
113
+ is_work_day = True
114
+ row['work'] = is_work_day
115
+
116
+ return table
@@ -0,0 +1,24 @@
1
+ # !/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+
4
+ """
5
+ @Time : 2023-12-29
6
+ @Author : Rey
7
+ @Contact : reyxbo@163.com
8
+ @Explain : Base methods.
9
+ """
10
+
11
+ from fake_useragent import UserAgent
12
+ from reykit.rbase import Base
13
+
14
+ __all__ = (
15
+ 'CrawlerBase',
16
+ 'ua'
17
+ )
18
+
19
+ class CrawlerBase(Base):
20
+ """
21
+ Crawler base type.
22
+ """
23
+
24
+ ua = UserAgent()
@@ -0,0 +1,371 @@
1
+ # !/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+
4
+ """
5
+ @Time : 2025-10-24
6
+ @Author : Rey
7
+ @Contact : reyxbo@163.com
8
+ @Explain : Browser methods.
9
+ """
10
+
11
+ from typing import Any, Literal
12
+ from traceback import format_exc
13
+ from enum import StrEnum
14
+ from selenium.webdriver import Edge, Chrome, EdgeOptions, ChromeOptions
15
+ from reydb import rorm, DatabaseEngine
16
+ from reykit.rbase import throw
17
+ from reykit.rnet import join_url
18
+ from reykit.rtime import TimeMark, now, sleep
19
+
20
+ from .rbase import CrawlerBase
21
+
22
+ __all__ = (
23
+ 'CrawlBrowserPageStatusEnum',
24
+ 'CrawlerORMTableCrawlBrowserPage',
25
+ 'CrawlerBrowser',
26
+ 'crawl_page',
27
+ 'add_db_crawl_task',
28
+ 'wait_db_crawl_task',
29
+ 'crawl_page_use_db'
30
+ )
31
+
32
+ class CrawlBrowserPageStatusEnum(CrawlerBase, StrEnum):
33
+ """
34
+ Crawl browser page status enumeration type.
35
+ """
36
+
37
+ WAIT = 'wait'
38
+ 'Wait crawl.'
39
+ SUCCESS = 'success'
40
+ 'Crawl successded.'
41
+ FAIL = 'fail'
42
+ 'Crawl failed.'
43
+ CANCEL = 'cancel'
44
+ 'Crawl cancelled.'
45
+
46
+ class CrawlerORMTableCrawlBrowserPage(CrawlerBase, rorm.Table):
47
+ """
48
+ Crawler `crawl_browser_page` table ORM model.
49
+ """
50
+
51
+ __name__ = 'crawl_browser_page'
52
+ __comment__ = 'Crawl browser page HTML text table.'
53
+ create_time: rorm.Datetime = rorm.Field(field_default=':time', not_null=True, index_n=True, comment='Record create time.')
54
+ update_time: rorm.Datetime | None = rorm.Field(field_default=':time', arg_default=now, index_n=True, comment='Record update time.')
55
+ id: int = rorm.Field(key_auto=True, comment='ID.')
56
+ url: str = rorm.Field(rorm.types.VARCHAR(8182), not_null=True, comment='Target URL.')
57
+ html: str | None = rorm.Field(rorm.types.TEXT, comment='Crawled HTML text.')
58
+ status: str = rorm.Field(rorm.ENUM(CrawlBrowserPageStatusEnum), field_default=CrawlBrowserPageStatusEnum.WAIT, not_null=True, comment='Crawl status.')
59
+ note: str | None = rorm.Field(rorm.types.VARCHAR(500), comment='Note.')
60
+
61
+ class CrawlerBrowser(CrawlerBase):
62
+ """
63
+ Control browser crawler type.
64
+ """
65
+
66
+ def __init__(
67
+ self,
68
+ driver: Literal['edge', 'chrome'] = 'edge',
69
+ headless: bool = True,
70
+ echo: bool = False,
71
+ db_engine: DatabaseEngine | None = None
72
+ ) -> None:
73
+ """
74
+ Build instance attributes.
75
+
76
+ Parameters
77
+ ----------
78
+ driver : Browser driver type.
79
+ - `Literal['edge']`: Edge browser.
80
+ - `Literal['chrome']`: Chrome browser.
81
+ headless : Whether use headless mode.
82
+ echo : Whether to print the report.
83
+ db_engine : Database engine.
84
+ - `None`: Not use database.
85
+ - `Database`: Automatic crawl page by database table.
86
+ """
87
+
88
+ # Parameter.
89
+ self.echo = echo
90
+ self.db_engine = db_engine
91
+ match driver:
92
+ case 'edge':
93
+ driver_type = Edge
94
+ driver_option_type = EdgeOptions
95
+ case 'chrome':
96
+ driver_type = Chrome
97
+ driver_option_type = ChromeOptions
98
+
99
+ # Build.
100
+
101
+ ## Build Database.
102
+ if self.db_engine is not None:
103
+ self.build_db()
104
+
105
+ ## Driver.
106
+ options = driver_option_type()
107
+ if headless:
108
+ options.add_argument('--headless')
109
+ self.driver = driver_type(options)
110
+
111
+ ## Crawl by database.
112
+ if self.db_engine is not None:
113
+ self.__loop_crawl_by_db()
114
+
115
+ def build_db(self) -> None:
116
+ """
117
+ Check and build database tables.
118
+ """
119
+
120
+ # Check.
121
+ if self.db_engine is None:
122
+ throw(ValueError, self.db_engine)
123
+
124
+ # Parameter.
125
+
126
+ ## Table.
127
+ tables = [CrawlerORMTableCrawlBrowserPage]
128
+
129
+ # Build.
130
+
131
+ ## WeChat.
132
+ self.db_engine.build(tables=tables, skip=True)
133
+
134
+ def __loop_crawl_by_db(self) -> None:
135
+ """
136
+ Loop crawl by database table.
137
+ """
138
+
139
+ # Echo.
140
+ if self.echo:
141
+ print('Start loop crawl by database table.')
142
+
143
+ # Loop.
144
+ while True:
145
+
146
+ ## Crawl.
147
+ self.__crawl_by_db()
148
+
149
+ ## Sleep.
150
+ sleep(1)
151
+
152
+ def __crawl_by_db(self) -> None:
153
+ """
154
+ Crawl by database table.
155
+ """
156
+
157
+ # Get task.
158
+ task_table = self.db_engine.execute.select(
159
+ 'crawl_browser_page',
160
+ ['id', 'url'],
161
+ f'"status" = \'{CrawlBrowserPageStatusEnum.WAIT}\'',
162
+ order='"create_time" ASC'
163
+ )
164
+
165
+ # Crawl.
166
+ for id_, url in task_table:
167
+ try:
168
+ self.request(url)
169
+ except BaseException:
170
+ exc_text = format_exc()
171
+ print(exc_text)
172
+ status = CrawlBrowserPageStatusEnum.FAIL
173
+ else:
174
+ status = CrawlBrowserPageStatusEnum.SUCCESS
175
+
176
+ ## Database.
177
+ data = {
178
+ 'id': id_,
179
+ 'update_time': ':NOW()',
180
+ 'html': self.page,
181
+ 'status': status
182
+ }
183
+ self.db_engine.execute.update('crawl_browser_page', data)
184
+
185
+ def request(
186
+ self,
187
+ url: str,
188
+ params: dict[str, Any] | None = None
189
+ ) -> None:
190
+ """
191
+ Request URL.
192
+
193
+ Parameters
194
+ ----------
195
+ url : URL.
196
+ params : URL parameters.
197
+ """
198
+
199
+ # Parameter.
200
+ params = params or {}
201
+ url = join_url(url, **params)
202
+
203
+ # Echo.
204
+ if self.echo:
205
+ print(f'Crawl URL "{url}"')
206
+
207
+ # Request.
208
+ self.driver.get(url)
209
+
210
+ @property
211
+ def page(self) -> str:
212
+ """
213
+ Return page elements document.
214
+
215
+ Returns
216
+ -------
217
+ Page elements document.
218
+ """
219
+
220
+ # Parameter.
221
+ page_source = self.driver.page_source
222
+
223
+ return page_source
224
+
225
+ __call__ = request
226
+
227
+ def crawl_page(
228
+ url: str,
229
+ params: dict[str, Any] | None = None
230
+ ) -> str:
231
+ """
232
+ Crawl page elements document.
233
+
234
+ Parameters
235
+ ----------
236
+ url : URL.
237
+ params : URL parameters.
238
+
239
+ Returns
240
+ -------
241
+ Page elements document.
242
+ """
243
+
244
+ # Parameter.
245
+ browser = CrawlerBrowser(headless=True)
246
+
247
+ # Request.
248
+ browser.request(url, params)
249
+
250
+ # Page.
251
+ page = browser.page
252
+
253
+ return page
254
+
255
+ def add_db_crawl_task(
256
+ db_engine: DatabaseEngine,
257
+ url: str,
258
+ params: dict[str, Any] | None = None,
259
+ note: str | None = None
260
+ ) -> int:
261
+ """
262
+ Add crawl task into database table.
263
+
264
+ Parameters
265
+ ----------
266
+ db_engine : Database engine.
267
+ url : Target URL.
268
+ params : URL parameters.
269
+ note : Note.
270
+
271
+ Returns
272
+ -------
273
+ Record ID.
274
+ """
275
+
276
+ # Parameter.
277
+ params = params or {}
278
+ url = join_url(url, **params)
279
+
280
+ # Inesrt.
281
+ data = {'url': url, 'note': note}
282
+ result = db_engine.execute.insert(
283
+ 'crawl_browser_page',
284
+ data,
285
+ returning='id'
286
+ )
287
+ record_id: int = result.scalar()
288
+
289
+ return record_id
290
+
291
+ def wait_db_crawl_task(
292
+ db_engine: DatabaseEngine,
293
+ record_id: int,
294
+ timeout: int = 60
295
+ ) -> str:
296
+ """
297
+ Wait crawl task of database table, and get HTML text.
298
+
299
+ Parameters
300
+ ----------
301
+ db_engine : Database engine.
302
+ record_id : Record ID.
303
+ timeout : Timeout seconds.
304
+
305
+ Returns
306
+ -------
307
+ HTML text.
308
+ """
309
+
310
+ # Loop.
311
+ tm = TimeMark()
312
+ tm()
313
+ while True:
314
+
315
+ # Select.
316
+ result = db_engine.execute.select(
317
+ 'crawl_browser_page',
318
+ ['html', 'status'],
319
+ '"id" = :record_id',
320
+ limit=1,
321
+ record_id=record_id
322
+ )
323
+
324
+ # Check.
325
+ if result.empty:
326
+ throw(AssertionError, record_id)
327
+
328
+ # Complete.
329
+ row = result.to_row()
330
+ if row['status'] == CrawlBrowserPageStatusEnum.SUCCESS:
331
+ html = row['html']
332
+ return html
333
+
334
+ # Timeout.
335
+ tm()
336
+ if tm.total_spend > timeout:
337
+ throw(TimeoutError, record_id)
338
+
339
+ # Sleep.
340
+ sleep(1)
341
+
342
+ def crawl_page_use_db(
343
+ db_engine: DatabaseEngine,
344
+ url: str,
345
+ params: dict[str, Any] | None = None,
346
+ note: str | None = None,
347
+ timeout: int = 60
348
+ ) -> str:
349
+ """
350
+ Add crawl task into database table, wait and get HTML text.
351
+
352
+ Parameters
353
+ ----------
354
+ db_engine : Database engine.
355
+ url : Target URL.
356
+ params : URL parameters.
357
+ note : Note.
358
+ timeout : Timeout seconds.
359
+
360
+ Returns
361
+ -------
362
+ HTML text.
363
+ """
364
+
365
+ # Add.
366
+ record_id = add_db_crawl_task(db_engine, url, params, note)
367
+
368
+ # Wait.
369
+ html = wait_db_crawl_task(db_engine, record_id, timeout)
370
+
371
+ return html