Python 2 中的 requests 库基于 urllib2 模块实现,因此有必要了解 urllib2 模块的 API 使用与原理。
本文将结合 requests 库,详细介绍 urllib2 模块。本文所有描述基于 Python 2。
在使用 urllib2 的 API 之前,简单介绍其中两个重要概念,opener 与 handler。
urllib2 中使用 opener 获取 URL(fetch a URL)。
调用 urllib2.urlopen 方法时创建默认 opener,也支持用户自定义。
opener 是 urllib2.OpenerDirector 实例,用于管理各种类型的 handler。
opener 通过 handler 完成请求的调用。
handler 是抽象的处理者,定义了处理请求的相关接口,不同 handler 中对应各种方法的不同实现。
自定义 opener 实际上就是自定义 handler。
分别使用 urllib2 的 API 完成无参 GTE 请求与传参请求。
urllib2 对外提供 urlopen 函数接口,用于获取 URL。
因此,请求调用可以仅一步完成:
>>> import urllib2
>>> response = urllib2.urlopen('http://python.org/')
response 对象是一个类文件对象。
>>> response
<addinfourl at 81364488L whose fp = <socket._fileobject object at 0x0000000004CC71C8>>
>>> resp.fileno()
744L
>>> resp.fp
<socket._fileobject object at 0x0000000004CC71C8>
因此,调用 read 方法可以获取响应正文,调用 readline 方法可以获取到正文的第一行。
>>> html = response.read()
>>> response.readline()
'<!doctype html>\n'
示例中调用 urllib2.urlopen 一个函数就可以完成请求响应的全过程,内部封装了多层处理,当然这种默认处理并不满足全部场景,因此特殊场景下需要调用内部处理的方法,下面介绍内部处理中用到的部分方法。
请求调用可以分两步完成:
先有请求对象,后有响应对象,这样也比较合乎逻辑。
>>> request = urllib2.Request('http://python.org/')
>>> response = urllib2.urlopen(request)
可见,urllib2.urlopen 函数的第一个参数 url 可以是 URL 地址,也可以是 Request 对象。
上面提到,urllib2 中使用 opener 获取 URL,到目前为止,使用的都是默认的 opener。
默认的 handler 仅支持基本功能,不支持代理、cookie 等其他的 HTTP/HTTPS 高级功能,这种情况下可以使用自定义 handler。
自定义 handler 时,请求调用可以分四步完成:
调用 urllib2.urlopen 一个函数完成的请求可以拆分为如下四步,作用相同。
# 构建 HTTPHandler 处理器对象,支持 HTTP 请求
>>> http_handler = urllib2.HTTPHandler()
# 创建支持处理 HTTP 请求的 opener 对象(OpenerDirector 实例对象)
>>> opener = urllib2.build_opener(http_handler)
>>> request = urllib2.Request('http://python.org/')
# 调用自定义 opener 对象的 open 方法,发送 request 请求
>>> response = opener.open(request)
当然,实际业务中不会这样使用。通常是当默认 handler 无法满足需求时,在不改动源码的前提下通过自定义 handler 实现功能扩展。
比如测试过程中可以通过设置 debuglevel 参数开启 debug log,从而将请求处理中的收包和发包的报头打印在日志中,而不需要人为抓包。
# 默认 debuglevel=0,表示关闭 debug log
>>> http_handler = urllib2.HTTPHandler(debuglevel=1)
比较普遍的应用是在爬虫/反爬虫过程中通过自定义 handler 使用代理 IP。
requests v0.2.0 中就使用到了 build_opener 方法。
如下所示,调用 _get_opener 方法创建 opener 方法时,如果不需要验证,直接返回 urllib.urlopen 方法,否则调用 build_opener 方法创建自定义 opener 并返回 urllib.open 方法。
def _get_opener(self):
"""Creates appropriate opener object for urllib2."""
if self.auth:
# 密码管理对象,create a password manager
authr = urllib2.HTTPPasswordMgrWithDefaultRealm()
authr.add_password(None, self.url, self.auth.username, self.auth.password)
handler = urllib2.HTTPBasicAuthHandler(authr)
opener = urllib2.build_opener(handler)
# use the opener to fetch a URL
return opener.open
else:
return urllib2.urlopen
再新增一步,请求调用可以分五步完成:
示例如下所示,其中在调用 install_opener 方法后调用 urllib2.urlopen 函数发起请求。
>>> http_handler = urllib2.HTTPHandler()
>>> opener = urllib2.build_opener(http_handler)
>>> urllib2.install_opener(opener)
>>> request = urllib2.Request('http://python.org/')
>>> response = urllib2.urlopen(request)
那么,哪种场景下需要调用 install_opener 方法呢?
如果之后全部请求都需要使用自定义代理(opener) ,可以调用 install_opener 方法将其设置为全局默认,这种情况下,无论调用 urllib2.urlopen,还是 opener.open 函数,都会使用自定义代理。不过通常不需要调用该方法。
requests v0.2.1 中就使用到了 install_opener 方法,将支持文件上传功能的两个自定义 handler 设置为默认 opener。
def register_openers():
"""Register the streaming http handlers in the global urllib2 default
opener object.
Returns the created OpenerDirector object."""
handlers = [StreamingHTTPHandler, StreamingHTTPRedirectHandler]
if hasattr(httplib, "HTTPS"):
handlers.append(StreamingHTTPSHandler)
opener = urllib2.build_opener(*handlers)
urllib2.install_opener(opener)
return opener
urllib2 模块完成 GET 请求可以分三步完成:
请求示例如下所示。
>>> import urllib
>>> import urllib2
# 请求地址与请求参数
>>> URL = "https://sl.se"
>>> params_dict = {"lat": "35.696233", "long": "139.570431"}
# 构建请求参数,拼接查询字符串
>>> params_encode = urllib.urlencode(params_dict)
>>> URL_params = "%s?%s" % (URL, params_encode)
>>> URL_params
'https://sl.se?lat=35.696233&long=139.570431'
# 发送请求
>>> response = urllib2.urlopen(URL_params)
# 处理响应
>>> response.getcode()
200
urllib2 模块完成 POST 请求可以分两步完成:
通过 data 参数区分 GET 与 POST 请求方法。
请求示例如下所示,注意其中没有拼接查询参数与 URL,而是将参数作为 data(body)传给 urlopen 方法。
>>> url = "http://fanyi.youdao.com/translate"
>>> form_data = {
"type":"AUTO",
"i":"i love python",
"doctype":"json",
"xmlVersion":"1.8",
"keyform":"fanyi.web",
"ue":"utf-8",
"action":"FY_BY_ENTER",
"typoResult":"true"
}
>>> data = urllib.urlencode(form_data)
>>> data
'keyform=fanyi.web&ue=utf-8&action=FY_BY_ENTER&i=i+love+python&xmlVersion=1.8&type=AUTO&doctype=json&typoResult=true'
>>> request = urllib2.Request(url, data=data)
>>> response = urllib2.urlopen(request)
>>> response.getcode()
200
调用 urlopen 方法是使用 urllib2 模块发起请求时最简单的调用方式。
函数的入参是 URL / Request 对象和数据,出参是类文件对象 response。
_opener = None
def urlopen(url, data=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT,
cafile=None, capath=None, cadefault=False, context=None):
global _opener
...
if context:
https_handler = HTTPSHandler(context=context)
opener = build_opener(https_handler)
elif _opener is None:
_opener = opener = build_opener()
else:
opener = _opener
return opener.open(url, data, timeout)
urlopen 中调用 build_opener 方法构建全局对象 _opener 并保存,然后调用 open 方法并传入 URL 与数据。
因此,如果程序中多次调用 urlopen,不需要重复构建 opener 对象。
如果 _opener 对象不为空,表明不是第一次调用 urlopen 或者已经调用 install_opener 方法修改了全局默认对象,不需要再构建 opener 对象。
install_opener 方法的实现非常简单,就是将自定义 handler 设置为全局默认。
def install_opener(opener):
global _opener
_opener = opener
从注释中也可以看到,urlopen 方法的基本用法与 urllib 库相同,其中一个区别是支持传入 request 实例。
urlopen(url, data=None) -- Basic usage is the same as original
urllib. pass the url and optionally data to post to an HTTP URL, and
get a file-like object back. One difference is that you can also pass
a Request instance instead of URL. Raises a URLError (subclass of
IOError); for HTTP errors, raises an HTTPError, which can also be
treated as a valid response.
可见,调用 urlopen 或 open 两种方法均可以完成请求的发送,不过最终都是调用 open 方法,因此 open 方法是 urllib2 的核心逻辑,将在源码解析的最后部分介绍。
下面首先介绍 build_opener 方法。
首先创建 handler,然后将其绑定到 opener。
build_opener 方法入参是 handlers,出参都是 OpenerDirector 对象,其中可以绑定各种不同的 handler。
def build_opener(*handlers):
"""Create an opener object from a list of handlers.
The opener will use several default handlers, including support
for HTTP, FTP and when applicable, HTTPS.
If any of the handlers passed as arguments are subclasses of the
default handlers, the default handlers will not be used.
"""
...
opener = OpenerDirector()
# 默认 handler
default_classes = [ProxyHandler, UnknownHandler, HTTPHandler,
HTTPDefaultErrorHandler, HTTPRedirectHandler,
FTPHandler, FileHandler, HTTPErrorProcessor]
if hasattr(httplib, 'HTTPS'):
default_classes.append(HTTPSHandler)
... # 对比去重
# 将 handler 绑定到 OpenerDirector 实例
for klass in default_classes:
opener.add_handler(klass())
for h in handlers:
if isclass(h):
h = h()
opener.add_handler(h)
return opener
注意 handlers 是可选参数,用于支持自定义 handler,如果没有的话,将使用默认 handler,如 HTTPHandler、HTTPSHandler。
build_opener 方法中在对比去重用户传入的自定义 handler 与默认 handler 后,统一调用 add_handler 方法将每个 handler 绑定到 OpenerDirector 实例。
如下所示,判断自定义 handler 与 默认 handler 的关系,如果前者是后者的子类或实例化对象,则删除默认 handler,以用户传入的 handler 为准。
skip = set()
for klass in default_classes:
for check in handlers:
if isclass(check): # 判断是否是类
if issubclass(check, klass): # 判断是否是子类
skip.add(klass)
elif isinstance(check, klass): # 判断是否是实例
skip.add(klass)
for klass in skip:
default_classes.remove(klass) # 删除默认 handler
从注释中也可以看到,build_opener 方法用于创建 OpenerDirector 实例,支持用户自定义 handler。如果自定义 handler 是默认 handler 的子类,则以自定义 handler 为准。
build_opener -- Function that creates a new OpenerDirector instance.
Will install the default handlers. Accepts one or more Handlers as
arguments, either instances or Handler classes that it will
instantiate. If one of the argument is a subclass of the default
handler, the argument will be installed instead of the default.
opener 用于管理 handler,而 handler 用于完成实际的请求。
opener 中会绑定多个 handler,而 opener 是 urllib2.OpenerDirector 实例,那么,OpenerDirector 中是如何管理各种 handler 的呢?
OpenerDirector 用于管理 handler,每种 handler 实现特定的协议,如 HTTPHandler、HTTPRedirectHandler。
The OpenerDirector manages a collection of Handler objects that do
all the actual work. Each Handler implements a particular protocol or
option. The OpenerDirector is a composite object that invokes the
Handlers needed to open the requested URL. For example, the
HTTPHandler performs HTTP GET and POST requests and deals with
non-error returns. The HTTPRedirectHandler automatically deals with
HTTP 301, 302, 303 and 307 redirect errors, and the HTTPDigestAuthHandler
deals with digest authentication.
OpenerDirector 类中有 4 个实例属性,分别管理不同类型的 handler,对应请求的不同处理过程,具体包括 open、 request、response、error。
class OpenerDirector:
def __init__(self):
...
# manage the individual handlers
self.handle_open = {}
self.handle_error = {}
self.process_response = {}
self.process_request = {}
初始化时 handle 字典为空,调用 add_handler 时会根据方法名将 handler 保存到对应的 handle 字典,字典的 value 是 handler,key 是网络协议名称。
对于一个 handler,调用 add_handler 方法时,如何判断应该保存到哪个字典呢?
handler 与字典的对应关系基于方法名的约定。
比如 HTTPHandler 中 http_open 方法名表示实现了 HTTP 协议 open 过程的方法。
实际上,handler 中的方法名只有以下几类:_error
、_open
、_request
、_response
,并约定以协议名称作为前缀。
因此,实现了哪类方法就属于哪种 handler,以哪种协议作为前缀就对应 handle 字典中的哪种协议。
add_handler 方法中遍历 handler 中的全部方法,分别按照_
分隔符拆分方法名,获取到协议名称与请求的处理过程。
def add_handler(self, handler):
...
for meth in dir(handler):
i = meth.find("_")
protocol = meth[:i]
condition = meth[i+1:]
获取到协议名称和处理过程以后,就可以根据处理过程,将不同的 handler 保存到对应的 handle 字典。
...
elif condition == "open":
kind = protocol
lookup = self.handle_open
elif condition == "response":
kind = protocol
lookup = self.process_response
elif condition == "request":
kind = protocol
lookup = self.process_request
...
到现在为止,就创建好了 handler 与 opener,下面就是调用 opener.open(url, data, timeout) 方法发起请求了。
open 方法中主要是分别使用 OpenerDirector 中的三个 handle 字典,包括 handle_open、process_response、process_request。
其中 process_request 用于请求预处理,process_response 用于处理响应,handle_open 用于发起请求获取响应,具体在 _open 方法中实现。
def open(self, fullurl, data=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT):
...
# 正则匹配 URL 识别网络协议
protocol = req.get_type()
# pre-process request
meth_name = protocol+"_request"
for processor in self.process_request.get(protocol, []):
meth = getattr(processor, meth_name)
req = meth(req)
response = self._open(req, data)
# post-process response
meth_name = protocol+"_response"
for processor in self.process_response.get(protocol, []):
meth = getattr(processor, meth_name)
response = meth(req, response)
return response
根据 URL 的网络协议获取字典中保存的 handler,然后依次遍历对应协议的 handler 进行处理。
比如对于 request 处理,首先会从 process_request 字典中获取出可以处理 HTTP 请求的 handler,HTTP 请求默认只有一个处理器:HTTPHandler, 因此会使用 HTTPHandler 中的 http_request 方法来处理,其余两个处理过程类似。
上面提到,urllib2.urlopen 函数的第一个参数 url 可以是 URL 也可以是 Request 对象。
原因是 open 方法中首先会判断参数类型,如果是 URL,会自动创建 Request 对象。
# accept a URL or a Request object
if isinstance(fullurl, basestring):
req = Request(fullurl, data)
else:
req = fullurl
if data is not None:
req.add_data(data)
...
_open 方法中主要是调用 _call_chain 方法执行网络协议对应的 handler。
def _open(self, req, data=None):
# 使用默认 handler
result = self._call_chain(self.handle_open, 'default',
'default_open', req)
if result:
return result
# 使用协议对应的 handler
protocol = req.get_type()
result = self._call_chain(self.handle_open, protocol, protocol +
'_open', req)
if result:
return result
# 协议异常或没有协议的 handler
return self._call_chain(self.handle_open, 'unknown',
'unknown_open', req)
_call_chain 中最终调用 handler 中实现的方法。
_call_chain 方法类似职责链模式,遍历协议对应的全部 handler,查找可以处理该请求的方法,然后调用。
def _call_chain(self, chain, kind, meth_name, *args):
# Handlers raise an exception if no one else should try to handle
# the request, or return None if they can't but another handler
# could. Otherwise, they return the response.
handlers = chain.get(kind, ())
for handler in handlers:
func = getattr(handler, meth_name)
result = func(*args)
if result is not None:
return result
责任链模式(Chain of Responsibility)是一种设计模式。
思想是通过将多个处理器(handler)串成链处理请求,请求在链上传递,直到其中某个处理成功为止。
在实际场景中,财务审批就是一个责任链模式。假设某个员工需要报销一笔费用,审核者可以分为:
用责任链模式设计此报销流程时,每个审核者只关心自己责任范围内的请求,并且处理它。对于超出自己责任范围的,扔给下一个审核者处理,这样,将来继续添加审核者的时候,不用改动现有逻辑。
职责链模式的优点是使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。
类似 workflow 的流程性事务处理过程就可以通过职责链模式实现。不同场景下的业务实现对应不同的 handler,将其组合成链以后,通过提供统一入口兼容变化的需求,扩展性强。
需要注意的是链中每个 handler 的顺序很重要,顺序不对可能导致处理结果异常。
职责链模式的实现方式有多种,其中 urlib2 中 _call_chain 方法中遍历全部 handler,如果返回 None,则交给下一个 handler 处理。
def _call_chain(self, chain, kind, meth_name, *args):
handlers = chain.get(kind, ())
for handler in handlers:
func = getattr(handler, meth_name)
result = func(*args)
if result is not None:
return result
可见,urllib2 基于职责链模式处理请求,并对外提供接口,以支持用户自定义 handler。
因此,urllib2 可以在不修改已有代码的前提下实现功能的扩展,这也符合开闭原则的要求。
Software entities should be open for extension,but closed for modification.
urllib2 是增强版的 urllib,因此实际上 urllib 也可以完成基础的请求调用。
如下所示,分别使用 urllib 的 API 发起 GET 与 POST 请求。
GET 请求
>>> import urllib
>>> params = urllib.urlencode({"lat": "35.696233", "long": "139.570431"})
>>> f = urllib.urlopen("https://sl.se?%s" % params)
>>> f.getcode()
200
POST 请求
>>> import urllib
>>> params = urllib.urlencode({"lat": "35.696233", "long": "139.570431"})
>>> f = urllib.urlopen("https://sl.se?", params)
>>> f.getcode()
200
urllib2 可以认为是增强版的 urllib,如:
而对于 urllib 中提供的功能,urllib2 中也没有重新造轮子,如:
如下所示,urllib2.AbstractHTTPHandler 类中将请求结果保存到 urllib.addinfourl 类,可见最终返回的 response 是 addinfourl 对象。
class AbstractHTTPHandler(BaseHandler):
def do_open(self, http_class, req, **http_conn_args):
...
from urllib import addinfourl
resp = addinfourl(fp, r.msg, req.get_full_url())
resp.code = r.status
resp.msg = r.reason
return resp
因此,Python 2 中通常 urllib 与 urllib2 两者结合使用。
不同的网络协议对应不同的处理过程,Request 类的 get_type 方法中通过拆分 URL 识别对应的网络协议。
def get_type(self):
if self.type is None:
self.type, self.__r_type = splittype(self.__original)
if self.type is None:
raise ValueError, "unknown url type: %s" % self.__original
return self.type
实际上是调用 urllib.splittype 函数,其中通过调用 re.match 方法匹配 URL 拆分网络协议与路径。
如果正则表达式不匹配,表明 URL 格式不合法,返回 None,否则返回协议名称。
_typeprog = None
def splittype(url):
global _typeprog
if _typeprog is None:
import re
_typeprog = re.compile('^([^/:]+):')
match = _typeprog.match(url)
if match:
scheme = match.group(1)
return scheme.lower(), url[len(scheme) + 1:]
return None, url
不过如果当前 URL 对应的网络协议暂不支持会怎么处理呢?
上面提到,_open 方法中会调用三次 _call_chain 方法,分别是默认 handler、协议对应的 handler、异常 handler。当协议不支持时,对应异常 handler。
def _open(self, req, data=None):
...
# 协议异常或没有协议的 handler
return self._call_chain(self.handle_open, 'unknown',
'unknown_open', req)
UnknownHandler 类的 unknown_open 方法中抛出 URLError 异常。
class UnknownHandler(BaseHandler):
def unknown_open(self, req):
type = req.get_type()
raise URLError('unknown url type: %s' % type)
urllib2 是增强版的 urllib,Python 2 中通常 urllib 与 urllib2 两者结合使用。
urllib2 中有两个重要概念,opener 与 handler。其中:
urllib2 最简单的调用方式是直接调用 urllib2.urlopen 方法,其中将使用默认 opener。
urllib2 基于职责链模式处理请求,并对外提供接口,以支持用户自定义 handler。
每种 handler 对应不同的网络协议,其中可以自定义请求处理过程中的具体实现,并约定方法名的格式为【网络协议 + 处理过程】。
本文来自读者朋友「鸟山明」的合作投稿
本文由哈喽比特于2年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/5vFfgb373CGMd3aQdHUajg
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。