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.
- reycrawler-1.0.7/.gitignore +10 -0
- reycrawler-1.0.7/.python-version +1 -0
- reycrawler-1.0.7/LICENSE +7 -0
- reycrawler-1.0.7/PKG-INFO +33 -0
- reycrawler-1.0.7/README.md +9 -0
- reycrawler-1.0.7/pyproject.toml +26 -0
- reycrawler-1.0.7/src/reycrawler/__init__.py +18 -0
- reycrawler-1.0.7/src/reycrawler/rall.py +20 -0
- reycrawler-1.0.7/src/reycrawler/rbaidu.py +116 -0
- reycrawler-1.0.7/src/reycrawler/rbase.py +24 -0
- reycrawler-1.0.7/src/reycrawler/rbrowser.py +371 -0
- reycrawler-1.0.7/src/reycrawler/rdouban.py +569 -0
- reycrawler-1.0.7/src/reycrawler/rother.py +55 -0
- reycrawler-1.0.7/src/reycrawler/rsina.py +234 -0
- reycrawler-1.0.7/src/reycrawler/rtoutiao.py +70 -0
- reycrawler-1.0.7/src/reycrawler/rweibo.py +89 -0
- reycrawler-1.0.7/uv.lock +1939 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.13
|
reycrawler-1.0.7/LICENSE
ADDED
|
@@ -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,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
|