requrst 0.5.0__tar.gz → 2.0.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: requrst
3
- Version: 0.5.0
3
+ Version: 2.0.0
4
4
  Summary: 纯Python Socket手写HTTP/HTTPS客户端,零第三方依赖,第二方自研请求库
5
5
  Home-page:
6
6
  Author:
@@ -0,0 +1,14 @@
1
+ from .core import (
2
+ get, post, Session, Response, RequestContext,
3
+ register_before_request, extract_html_text, send_email,
4
+ DEFAULT_VERIFY, DEFAULT_TIMEOUT, DEFAULT_RETRY,
5
+ DEFAULT_MAX_REDIRECTS, DEFAULT_HEADERS
6
+ )
7
+
8
+ __version__ = "2.0.0"
9
+ __all__ = [
10
+ "get", "post", "Session", "Response", "RequestContext",
11
+ "register_before_request", "extract_html_text", "send_email",
12
+ "DEFAULT_VERIFY", "DEFAULT_TIMEOUT", "DEFAULT_RETRY",
13
+ "DEFAULT_MAX_REDIRECTS", "DEFAULT_HEADERS"
14
+ ]
@@ -0,0 +1,333 @@
1
+ import socket
2
+ import ssl
3
+ import json
4
+ import re
5
+ from urllib.parse import urlparse, urlencode
6
+ import base64
7
+
8
+ DEFAULT_HEADERS = {
9
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
10
+ "Connection": "close"
11
+ }
12
+ DEFAULT_TIMEOUT = 10
13
+ DEFAULT_RETRY = 1
14
+ DEFAULT_MAX_REDIRECTS = 3
15
+ DEFAULT_VERIFY = True
16
+
17
+ def recv_smtp_line(sock):
18
+ resp = b""
19
+ while True:
20
+ chunk = sock.recv(1)
21
+ if not chunk:
22
+ break
23
+ resp += chunk
24
+ if resp.endswith(b"\n"):
25
+ break
26
+ return resp.decode("utf-8", errors="ignore").strip()
27
+
28
+ def recv_smtp_multiline(sock):
29
+ lines = []
30
+ while True:
31
+ line = recv_smtp_line(sock)
32
+ lines.append(line)
33
+ if not line.startswith("250-"):
34
+ break
35
+ return "\n".join(lines)
36
+
37
+ def _parse_chunked_body(data: bytes) -> bytes:
38
+ result = b""
39
+ offset = 0
40
+ while True:
41
+ crlf_pos = data.find(b"\r\n", offset)
42
+ if crlf_pos == -1:
43
+ break
44
+ chunk_size_hex = data[offset:crlf_pos].decode("ascii").strip()
45
+ try:
46
+ chunk_size = int(chunk_size_hex, 16)
47
+ except ValueError:
48
+ break
49
+ offset = crlf_pos + 2
50
+ if chunk_size == 0:
51
+ break
52
+ result += data[offset:offset + chunk_size]
53
+ offset += chunk_size + 2
54
+ return result
55
+
56
+ class Response:
57
+ def __init__(self, status_code: int, headers: dict, content: bytes):
58
+ self.status_code = status_code
59
+ self.headers = {k.lower(): v for k, v in headers.items()}
60
+ self.content = content
61
+ self.encoding = self._auto_detect_encoding()
62
+
63
+ @property
64
+ def text(self):
65
+ return self.content.decode(self.encoding, errors="ignore")
66
+
67
+ def _auto_detect_encoding(self):
68
+ content_type = self.headers.get("content-type", "")
69
+ if "charset=" in content_type:
70
+ charset = content_type.split("charset=")[1].split(";")[0].strip().lower()
71
+ return charset
72
+ try:
73
+ self.content.decode("utf-8")
74
+ return "utf-8"
75
+ except UnicodeDecodeError:
76
+ return "gbk"
77
+
78
+ def json(self):
79
+ return json.loads(self.text)
80
+
81
+ def text_only(self):
82
+ text = re.sub(r"<script.*?</script>", "", self.text, flags=re.S | re.I)
83
+ text = re.sub(r"<style.*?</style>", "", text, flags=re.S | re.I)
84
+ text = re.sub(r"<!--.*?-->", "", text, flags=re.S)
85
+ text = re.sub(r"<.*?>", "", text)
86
+ text = re.sub(r"\s+", " ", text).strip()
87
+ return text
88
+
89
+ class RequestContext:
90
+ def __init__(self, url: str, method: str, headers: dict, timeout: int):
91
+ self.url = url
92
+ self.method = method
93
+ self.headers = headers
94
+ self.timeout = timeout
95
+
96
+ before_request_hooks = []
97
+
98
+ def register_before_request(func):
99
+ before_request_hooks.append(func)
100
+
101
+ def _run_before_intercept(ctx: RequestContext):
102
+ for hook in before_request_hooks:
103
+ hook(ctx)
104
+
105
+ def _socket_request(host, path, port, scheme, method, headers, body=b"", timeout=10, verify=True, retry=1, max_redirects=3):
106
+ for attempt in range(retry + 1):
107
+ sock = None
108
+ try:
109
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
110
+ sock.settimeout(timeout)
111
+
112
+ if scheme == "https":
113
+ ctx = ssl.create_default_context() if verify else ssl._create_unverified_context()
114
+ sock = ctx.wrap_socket(sock, server_hostname=host)
115
+
116
+ sock.connect((host, port))
117
+
118
+ req_lines = [f"{method} {path} HTTP/1.1", f"Host: {host}"]
119
+ for k, v in headers.items():
120
+ req_lines.append(f"{k}: {v}")
121
+ if body:
122
+ req_lines.append(f"Content-Length: {len(body)}")
123
+ req_lines.append("")
124
+ req_data = "\r\n".join(req_lines).encode("utf-8") + body
125
+ sock.sendall(req_data)
126
+
127
+ sock_file = sock.makefile("rb")
128
+ status_line = sock_file.readline().decode("utf-8").strip()
129
+ parts = status_line.split()
130
+ status_code = int(parts[1]) if len(parts) > 1 else 0
131
+
132
+ headers_dict = {}
133
+ while True:
134
+ line = sock_file.readline().decode("utf-8").strip()
135
+ if not line:
136
+ break
137
+ if ": " in line:
138
+ k, v = line.split(": ", 1)
139
+ headers_dict[k] = v
140
+
141
+ transfer_encoding = headers_dict.get("Transfer-Encoding", "").lower()
142
+ if transfer_encoding == "chunked":
143
+ chunked_data = b""
144
+ while True:
145
+ line = sock_file.readline().decode("utf-8").strip()
146
+ if not line:
147
+ break
148
+ chunk_size = int(line, 16)
149
+ if chunk_size == 0:
150
+ sock_file.readline()
151
+ break
152
+ chunk = sock_file.read(chunk_size)
153
+ chunked_data += chunk
154
+ sock_file.readline()
155
+ body_part = chunked_data
156
+ else:
157
+ content_length = headers_dict.get("Content-Length")
158
+ if content_length:
159
+ body_part = sock_file.read(int(content_length))
160
+ else:
161
+ body_part = sock_file.read()
162
+ sock_file.close()
163
+ sock.close()
164
+
165
+ if status_code in (301, 302, 303, 307, 308) and max_redirects > 0:
166
+ location = headers_dict.get("Location", "")
167
+ if location:
168
+ parsed = urlparse(location)
169
+ if not parsed.netloc:
170
+ location = f"{scheme}://{host}{location if location.startswith('/') else '/' + location}"
171
+ new_method = "GET" if status_code in (302, 303) else method
172
+ return _request(location, new_method, headers, body if new_method != "GET" else b"",
173
+ timeout, verify, retry, max_redirects - 1)
174
+
175
+ return Response(status_code, headers_dict, body_part)
176
+
177
+ except (socket.timeout, ConnectionError, ssl.SSLError) as e:
178
+ if sock:
179
+ sock.close()
180
+ if attempt == retry:
181
+ return Response(0, {}, f"Request Failed: {str(e)}".encode())
182
+ continue
183
+ except Exception as e:
184
+ if sock:
185
+ sock.close()
186
+ return Response(0, {}, f"Request Error: {str(e)}".encode())
187
+
188
+ def _request(url, method, headers=None, body=b"", timeout=10, verify=True, retry=1, max_redirects=3):
189
+ parsed = urlparse(url)
190
+ host = parsed.hostname
191
+ port = parsed.port or (443 if parsed.scheme == "https" else 80)
192
+ path = parsed.path or "/"
193
+ if parsed.query:
194
+ path += "?" + parsed.query
195
+
196
+ req_headers = DEFAULT_HEADERS.copy()
197
+ if headers:
198
+ req_headers.update(headers)
199
+ if "Host" not in req_headers:
200
+ req_headers["Host"] = host
201
+
202
+ ctx = RequestContext(url, method, req_headers, timeout)
203
+ _run_before_intercept(ctx)
204
+
205
+ return _socket_request(host, path, port, parsed.scheme, method,
206
+ ctx.headers, body, timeout, verify, retry, max_redirects)
207
+
208
+ class Session:
209
+ def __init__(self):
210
+ self.cookies = {}
211
+ self.headers = DEFAULT_HEADERS.copy()
212
+ self.timeout = DEFAULT_TIMEOUT
213
+ self.retry = DEFAULT_RETRY
214
+ self.max_redirects = DEFAULT_MAX_REDIRECTS
215
+ self.verify = DEFAULT_VERIFY
216
+
217
+ def _update_cookies(self, resp_headers):
218
+ set_cookie = resp_headers.get("Set-Cookie", "")
219
+ if not set_cookie:
220
+ return
221
+ for cookie in set_cookie.split(", "):
222
+ if "=" in cookie:
223
+ key_val = cookie.split(";")[0]
224
+ if "=" in key_val:
225
+ k, v = key_val.split("=", 1)
226
+ self.cookies[k.strip()] = v.strip()
227
+
228
+ def _build_cookie_header(self):
229
+ return "; ".join([f"{k}={v}" for k, v in self.cookies.items()]) if self.cookies else ""
230
+
231
+ def request(self, method, url, params=None, data=None, json_data=None, headers=None, **kwargs):
232
+ if params:
233
+ query = urlencode(params)
234
+ url += f"&{query}" if "?" in url else f"?{query}"
235
+
236
+ req_headers = self.headers.copy()
237
+ if headers:
238
+ req_headers.update(headers)
239
+
240
+ cookie_header = self._build_cookie_header()
241
+ if cookie_header:
242
+ req_headers["Cookie"] = cookie_header
243
+
244
+ body = b""
245
+ if json_data:
246
+ req_headers["Content-Type"] = "application/json; charset=utf-8"
247
+ body = json.dumps(json_data).encode("utf-8")
248
+ elif data:
249
+ req_headers["Content-Type"] = "application/x-www-form-urlencoded; charset=utf-8"
250
+ body = urlencode(data).encode("utf-8")
251
+
252
+ timeout = kwargs.get("timeout", self.timeout)
253
+ verify = kwargs.get("verify", self.verify)
254
+ retry = kwargs.get("retry", self.retry)
255
+ max_redirects = kwargs.get("max_redirects", self.max_redirects)
256
+
257
+ resp = _request(url, method, req_headers, body, timeout, verify, retry, max_redirects)
258
+ self._update_cookies(resp.headers)
259
+ return resp
260
+
261
+ def get(self, url, params=None, headers=None, **kwargs):
262
+ return self.request("GET", url, params=params, headers=headers, **kwargs)
263
+
264
+ def post(self, url, data=None, json_data=None, headers=None, **kwargs):
265
+ return self.request("POST", url, data=data, json_data=json_data, headers=headers, **kwargs)
266
+
267
+ def get(url, params=None, headers=None, **kwargs):
268
+ return Session().get(url, params=params, headers=headers, **kwargs)
269
+
270
+ def post(url, data=None, json_data=None, headers=None, **kwargs):
271
+ return Session().post(url, data=data, json_data=json_data, headers=headers, **kwargs)
272
+
273
+ def extract_html_text(html: str) -> str:
274
+ text = re.sub(r"<script.*?</script>", "", html, flags=re.S | re.I)
275
+ text = re.sub(r"<style.*?</style>", "", text, flags=re.S | re.I)
276
+ text = re.sub(r"<!--.*?-->", "", text, flags=re.S)
277
+ text = re.sub(r"<.*?>", "", text)
278
+ text = re.sub(r"\s+", " ", text).strip()
279
+ return text
280
+
281
+
282
+ def send_email(smtp_server, smtp_port, sender_email, sender_pwd, to_email, subject, content):
283
+ try:
284
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
285
+ ctx = ssl.create_default_context()
286
+ ssl_sock = ctx.wrap_socket(sock, server_hostname=smtp_server)
287
+ ssl_sock.connect((smtp_server, smtp_port))
288
+
289
+ recv_smtp_line(ssl_sock)
290
+ ssl_sock.send(b"EHLO client\r\n")
291
+ recv_smtp_multiline(ssl_sock)
292
+
293
+ ssl_sock.send(b"AUTH LOGIN\r\n")
294
+ resp = recv_smtp_line(ssl_sock)
295
+ if not resp.startswith("334"):
296
+ return False, "服务器不支持 AUTH LOGIN"
297
+
298
+ ssl_sock.send(base64.b64encode(sender_email.encode()) + b"\r\n")
299
+ resp = recv_smtp_line(ssl_sock)
300
+ if not resp.startswith("334"):
301
+ return False, "用户名被拒绝"
302
+
303
+ ssl_sock.send(base64.b64encode(sender_pwd.encode()) + b"\r\n")
304
+ resp = recv_smtp_line(ssl_sock)
305
+ if not resp.startswith("235"):
306
+ return False, "密码错误或认证失败"
307
+
308
+ ssl_sock.send(f"MAIL FROM:<{sender_email}>\r\n".encode())
309
+ resp = recv_smtp_line(ssl_sock)
310
+ if not resp.startswith("250"):
311
+ return False, f"发件人地址被拒绝: {resp}"
312
+
313
+ ssl_sock.send(f"RCPT TO:<{to_email}>\r\n".encode())
314
+ resp = recv_smtp_line(ssl_sock)
315
+ if not resp.startswith("250"):
316
+ return False, f"收件人地址被拒绝: {resp}"
317
+
318
+ ssl_sock.send(b"DATA\r\n")
319
+ resp = recv_smtp_line(ssl_sock)
320
+ if not resp.startswith("354"):
321
+ return False, "服务器拒绝接收数据"
322
+
323
+ msg = f"From: {sender_email}\r\nTo: {to_email}\r\nSubject: {subject}\r\nContent-Type: text/plain; charset=utf-8\r\n\r\n{content}\r\n.\r\n"
324
+ ssl_sock.send(msg.encode())
325
+ resp = recv_smtp_line(ssl_sock)
326
+ if not resp.startswith("250"):
327
+ return False, f"邮件发送失败: {resp}"
328
+
329
+ ssl_sock.send(b"QUIT\r\n")
330
+ ssl_sock.close()
331
+ return True, "邮件发送成功"
332
+ except Exception as e:
333
+ return False, f"发送失败: {str(e)}"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: requrst
3
- Version: 0.5.0
3
+ Version: 2.0.0
4
4
  Summary: 纯Python Socket手写HTTP/HTTPS客户端,零第三方依赖,第二方自研请求库
5
5
  Home-page:
6
6
  Author:
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
2
2
 
3
3
  setup(
4
4
  name="requrst",
5
- version="0.5.0",
5
+ version="2.0.0",
6
6
  author="",
7
7
  author_email="",
8
8
  description="纯Python Socket手写HTTP/HTTPS客户端,零第三方依赖,第二方自研请求库",
@@ -1,17 +0,0 @@
1
- from .core import (
2
- get,
3
- post,
4
- Session,
5
- Response,
6
- RequestContext,
7
- register_before_request,
8
- extract_html_text,
9
- send_email,
10
- DEFAULT_VERIFY,
11
- DEFAULT_TIMEOUT,
12
- DEFAULT_RETRY,
13
- DEFAULT_MAX_REDIRECTS
14
- )
15
-
16
- __version__ = "0.5.0"
17
-
@@ -1,303 +0,0 @@
1
- import socket
2
- import ssl
3
- import json
4
- import re
5
- from urllib.parse import urlparse, urlencode
6
- import smtplib
7
- from email.mime.text import MIMEText
8
- from email.mime.multipart import MIMEMultipart
9
-
10
- DEFAULT_HEADERS = {
11
- "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
12
- "Connection": "close"
13
- }
14
- DEFAULT_TIMEOUT = 10
15
- DEFAULT_RETRY = 1
16
- DEFAULT_MAX_REDIRECTS = 3
17
- DEFAULT_VERIFY = True
18
-
19
- class RequestContext:
20
- def __init__(self, url: str, method: str, headers: dict, timeout: int):
21
- self.url = url
22
- self.method = method
23
- self.headers = headers
24
- self.timeout = timeout
25
-
26
- class Response:
27
- def __init__(self, status_code: int, headers: dict, content: bytes):
28
- self.status_code = status_code
29
- self.headers = headers
30
- self.content = content
31
- self.encoding = self._auto_detect_encoding()
32
-
33
- @property
34
- def text(self):
35
- return self.content.decode(self.encoding, errors="ignore")
36
-
37
- def _auto_detect_encoding(self):
38
- content_type = self.headers.get("content-type", "")
39
- if "charset=" in content_type:
40
- charset = content_type.split("charset=")[1].split(";")[0].strip().lower()
41
- return charset
42
- try:
43
- self.content.decode("utf-8")
44
- return "utf-8"
45
- except UnicodeDecodeError:
46
- return "gbk"
47
-
48
- def json(self):
49
- return json.loads(self.text)
50
-
51
- def text_only(self):
52
- text = re.sub(r"<script.*?</script>", "", self.text, flags=re.S | re.I)
53
- text = re.sub(r"<style.*?</style>", "", text, flags=re.S | re.I)
54
- text = re.sub(r"<!--.*?-->", "", text, flags=re.S)
55
- text = re.sub(r"<.*?>", "", text)
56
- text = re.sub(r"\s+", " ", text).strip()
57
- return text
58
-
59
- before_request_hooks = []
60
-
61
- def register_before_request(func):
62
- before_request_hooks.append(func)
63
-
64
- def _run_before_intercept(ctx: RequestContext):
65
- for hook in before_request_hooks:
66
- hook(ctx)
67
-
68
- def _parse_chunked_body(data: bytes) -> bytes:
69
- result = b""
70
- offset = 0
71
- while True:
72
- crlf_pos = data.find(b"\r\n", offset)
73
- if crlf_pos == -1:
74
- break
75
- chunk_size_hex = data[offset:crlf_pos].decode("ascii").strip()
76
- chunk_size = int(chunk_size_hex, 16)
77
- offset = crlf_pos + 2
78
- if chunk_size == 0:
79
- break
80
- result += data[offset:offset+chunk_size]
81
- offset += chunk_size + 2
82
- return result
83
-
84
- def _socket_request(
85
- host, path, port, scheme, method, headers, body=b"",
86
- timeout=10, verify=DEFAULT_VERIFY, retry=1, max_redirects=3
87
- ):
88
- for attempt in range(retry + 1):
89
- sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
90
- sock.settimeout(timeout)
91
- try:
92
- if scheme == "https":
93
- context = ssl.create_default_context() if verify else ssl._create_unverified_context()
94
- sock = context.wrap_socket(sock, server_hostname=host)
95
-
96
- sock.connect((host, port))
97
-
98
- req_lines = [f"{method} {path} HTTP/1.1", f"Host: {host}"]
99
- for k, v in headers.items():
100
- req_lines.append(f"{k}: {v}")
101
- if body:
102
- req_lines.append(f"Content-Length: {len(body)}")
103
- req_lines.append("")
104
-
105
- req_data = "\r\n".join(req_lines).encode("utf-8") + body
106
- sock.sendall(req_data)
107
-
108
- resp_data = b""
109
- while True:
110
- chunk = sock.recv(4096)
111
- if not chunk:
112
- break
113
- resp_data += chunk
114
- sock.close()
115
-
116
- header_end = resp_data.find(b"\r\n\r\n")
117
- if header_end == -1:
118
- raise Exception("Invalid HTTP response")
119
- header_part = resp_data[:header_end].decode("utf-8", errors="ignore")
120
- body_part = resp_data[header_end+4:]
121
-
122
- first_line = header_part.splitlines()[0]
123
- status_code = int(first_line.split()[1])
124
-
125
- resp_headers = {}
126
- for line in header_part.splitlines()[1:]:
127
- if ": " in line:
128
- k, v = line.split(": ", 1)
129
- resp_headers[k.strip().lower()] = v.strip()
130
-
131
- if resp_headers.get("transfer-encoding", "").lower() == "chunked":
132
- body_part = _parse_chunked_body(body_part)
133
-
134
- if status_code in (301, 302) and max_redirects > 0:
135
- location = resp_headers.get("location", "")
136
- if not location:
137
- break
138
-
139
- new_method = "GET"
140
- new_body = b""
141
-
142
- redirect_url = urlparse(location)
143
- if not redirect_url.netloc:
144
- redirect_url = urlparse(f"{scheme}://{host}{location}")
145
- hp = redirect_url.netloc.split(":")
146
- rh = hp[0]
147
- rp = int(hp[1]) if len(hp) > 1 else (443 if redirect_url.scheme == "https" else 80)
148
- return _socket_request(
149
- rh,
150
- redirect_url.path or "/",
151
- rp,
152
- redirect_url.scheme,
153
- new_method, headers, new_body,
154
- timeout, verify, retry, max_redirects-1
155
- )
156
-
157
- return Response(status_code, resp_headers, body_part)
158
-
159
- except (socket.timeout, ConnectionError, ssl.SSLError) as e:
160
- sock.close()
161
- if attempt == retry:
162
- return Response(0, {}, f"Request Failed: {str(e)}".encode("utf-8"))
163
- continue
164
- except Exception as e:
165
- sock.close()
166
- return Response(0, {}, f"Request Error: {str(e)}".encode("utf-8"))
167
-
168
- class Session:
169
- def __init__(self):
170
- self.cookies = {}
171
- self.headers = DEFAULT_HEADERS.copy()
172
- self.timeout = DEFAULT_TIMEOUT
173
- self.retry = DEFAULT_RETRY
174
- self.max_redirects = DEFAULT_MAX_REDIRECTS
175
- self.verify = DEFAULT_VERIFY
176
-
177
- def _update_cookies(self, resp_headers):
178
- set_cookie = resp_headers.get("set-cookie", "")
179
- if not set_cookie:
180
- return
181
- for cookie in set_cookie.split(", "):
182
- if "=" in cookie:
183
- key_val = cookie.split(";")[0]
184
- if "=" in key_val:
185
- k, v = key_val.split("=", 1)
186
- self.cookies[k.strip()] = v.strip()
187
-
188
- def _build_cookie_header(self):
189
- return "; ".join([f"{k}={v}" for k, v in self.cookies.items()]) if self.cookies else ""
190
-
191
- def get(self, url: str, params: dict = None, headers: dict = None, **kwargs) -> Response:
192
- if params:
193
- query = urlencode(params)
194
- url += f"&{query}" if "?" in url else f"?{query}"
195
-
196
- req_headers = self.headers.copy()
197
- if headers:
198
- req_headers.update(headers)
199
- cookie_header = self._build_cookie_header()
200
- if cookie_header:
201
- req_headers["Cookie"] = cookie_header
202
-
203
- ctx = RequestContext(url, "GET", req_headers, kwargs.get("timeout", self.timeout))
204
- _run_before_intercept(ctx)
205
-
206
- parsed = urlparse(ctx.url)
207
- host_port = parsed.netloc.split(":")
208
- host = host_port[0]
209
- port = int(host_port[1]) if len(host_port) > 1 else (443 if parsed.scheme == "https" else 80)
210
- path = parsed.path if parsed.path else "/"
211
- if parsed.query:
212
- path += "?" + parsed.query
213
- scheme = parsed.scheme
214
-
215
- resp = _socket_request(
216
- host, path, port, scheme, "GET", ctx.headers, b"",
217
- timeout=kwargs.get("timeout", self.timeout),
218
- verify=kwargs.get("verify", self.verify),
219
- retry=kwargs.get("retry", self.retry),
220
- max_redirects=kwargs.get("max_redirects", self.max_redirects)
221
- )
222
- self._update_cookies(resp.headers)
223
- return resp
224
-
225
- def post(self, url: str, data: dict = None, json_data: dict = None, headers: dict = None, **kwargs) -> Response:
226
- req_headers = self.headers.copy()
227
- if headers:
228
- req_headers.update(headers)
229
- cookie_header = self._build_cookie_header()
230
- if cookie_header:
231
- req_headers["Cookie"] = cookie_header
232
-
233
- body = b""
234
- if json_data:
235
- req_headers["Content-Type"] = "application/json; charset=utf-8"
236
- body = json.dumps(json_data).encode("utf-8")
237
- elif data:
238
- req_headers["Content-Type"] = "application/x-www-form-urlencoded; charset=utf-8"
239
- body = urlencode(data).encode("utf-8")
240
-
241
- ctx = RequestContext(url, "POST", req_headers, kwargs.get("timeout", self.timeout))
242
- _run_before_intercept(ctx)
243
-
244
- parsed = urlparse(ctx.url)
245
- host_port = parsed.netloc.split(":")
246
- host = host_port[0]
247
- port = int(host_port[1]) if len(host_port) > 1 else (443 if parsed.scheme == "https" else 80)
248
- path = parsed.path if parsed.path else "/"
249
- if parsed.query:
250
- path += "?" + parsed.query
251
- scheme = parsed.scheme
252
-
253
- resp = _socket_request(
254
- host, path, port, scheme, "POST", ctx.headers, body,
255
- timeout=kwargs.get("timeout", self.timeout),
256
- verify=kwargs.get("verify", self.verify),
257
- retry=kwargs.get("retry", self.retry),
258
- max_redirects=kwargs.get("max_redirects", self.max_redirects)
259
- )
260
- self._update_cookies(resp.headers)
261
- return resp
262
-
263
- def get(url: str, params: dict = None, headers: dict = None, **kwargs) -> Response:
264
- return Session().get(url, params, headers,** kwargs)
265
-
266
- def post(url: str, data: dict = None, json_data: dict = None, headers: dict = None, **kwargs) -> Response:
267
- return Session().post(url, data, json_data, headers, **kwargs)
268
-
269
- def extract_html_text(html: str) -> str:
270
- text = re.sub(r"<script.*?</script>", "", html, flags=S | re.I)
271
- text = re.sub(r"<style.*?</style>", "", text, flags=re.S | re.I)
272
- text = re.sub(r"<!--.*?-->", "", text, flags=re.S)
273
- text = re.sub(r"<.*?>", "", text)
274
- text = re.sub(r"\s+", " ", text).strip()
275
- return text
276
-
277
- def send_email(
278
- smtp_server: str,
279
- smtp_port: int,
280
- sender_email: str,
281
- sender_pwd: str,
282
- to_email: str,
283
- subject: str,
284
- content: str
285
- ):
286
- msg = MIMEMultipart()
287
- msg["From"] = sender_email
288
- msg["To"] = to_email
289
- msg["Subject"] = subject
290
- msg.attach(MIMEText(content, "plain", "utf-8"))
291
-
292
- try:
293
- if smtp_port == 465:
294
- server = smtplib.SMTP_SSL(smtp_server, smtp_port)
295
- else:
296
- server = smtplib.SMTP(smtp_server, smtp_port)
297
- server.starttls()
298
- server.login(sender_email, sender_pwd)
299
- server.sendmail(sender_email, to_email, msg.as_string())
300
- server.quit()
301
- return True, "邮件发送成功"
302
- except Exception as e:
303
- return False, f"发送失败: {str(e)}"
File without changes
File without changes
File without changes