def response(flow): url = 'https://dedao.igetget.com/v3/discover/bookList' if flow.request.url.startswith(url): text = flow.response.text data = json.loads(text) books = data.get('c').get('list') for book in books: ctx.log.info(str(book))
import json import pymongo from mitmproxy import ctx
client = pymongo.MongoClient('localhost') db = client['igetget'] collection = db['books']
def response(flow): global collection url = 'https://dedao.igetget.com/v3/discover/bookList' if flow.request.url.startswith(url): text = flow.response.text data = json.loads(text) books = data.get('c').get('list') for book in books: data = {'title': book.get('operating_title'), 'cover': book.get('cover'), 'summary': book.get('other_share_summary'), 'price': book.get('price') } ctx.log.info(str(data)) collection.insert(data)
Charles 是一个网络抓包工具,我们可以用它来做 App 的抓包分析,得到 App 运行过程中发生的所有网络请求和响应内容,这就和 Web 端浏览器的开发者工具 Network 部分看到的结果一致。 相比 Fiddler 来说,Charles 的功能更强大,而且跨平台支持更好。所以我们选用 Charles 作为主要的移动端抓包工具,用于分析移动 App 的数据包,辅助完成 App 数据抓取工作。
1. 本节目标
本节我们以京东 App 为例,通过 Charles 抓取 App 运行过程中的网络数据包,然后查看具体的 Request 和 Response 内容,以此来了解 Charles 的用法。
2. 准备工作
请确保已经正确安装 Charles 并开启了代理服务,手机和 Charles 处于同一个局域网下,Charles 代理和 CharlesCA 证书设置好,另外需要开启 SSL 监听,具体的配置可以参考第 1 章的说明。
3. 原理
首先 Charles 运行在自己的 PC 上,Charles 运行的时候会在 PC 的 8888 端口开启一个代理服务,这个服务实际上是一个 HTTP/HTTPS 的代理。 确保手机和 PC 在同一个局域网内,我们可以使用手机模拟器通过虚拟网络连接,也可以使用手机真机和 PC 通过无线网络连接。 设置手机代理为 Charles 的代理地址,这样手机访问互联网的数据包就会流经 Charles,Charles 再转发这些数据包到真实的服务器,服务器返回的数据包再由 Charles 转发回手机,Charles 就起到中间人的作用,所有流量包都可以捕捉到,因此所有 HTTP 请求和响应都可以捕获到。同时 Charles 还有权力对请求和响应进行修改。
4. 抓包
初始状态下 Charles 的运行界面如图 11-1 所示: 图 11-1 Charles 运行界面 Charles 会一直监听 PC 和手机发生的网络数据包,捕获到的数据包就会显示在左侧,随着时间的推移,捕获的数据包越来越多,左侧列表的内容也会越来越多。 可以看到,图中左侧显示了 Charles 抓取到的请求站点,我们点击任意一个条目便可以查看对应请求的详细信息,其中包括 Request、Response 等内容。 接下来清空 Charles 的抓取结果,点击左侧的扫帚按钮即可清空当前捕获到的所有请求。然后点击第二个监听按钮,确保监听按钮是打开的,这表示 Charles 正在监听 App 的网络数据流,如图 11-2 所示。 图 11-2 监听过程 这时打开手机京东,注意一定要提前设置好 Charles 的代理并配置好 CA 证书,否则没有效果。 打开任意一个商品,如 iPhone,然后打开它的商品评论页面,如图 11-3 所示。 图 11-3 评论页面 不断上拉加载评论,可以看到 Charles 捕获到这个过程中京东 App 内发生的所有网络请求,如图 11-4 所示。 图 11-4 监听结果 左侧列表中会出现一个 api.m.jd.com 链接,而且它在不停闪动,很可能就是当前 App 发出的获取评论数据的请求被 Charles 捕获到了。我们点击将其展开,继续上拉刷新评论。随着上拉的进行,此处又会出现一个个网络请求记录,这时新出现的数据包请求确定就是获取评论的请求。 为了验证其正确性,我们点击查看其中一个条目的详情信息。切换到 Contents 选项卡,这时我们发现一些 JSON 数据,核对一下结果,结果有 commentData 字段,其内容和我们在 App 中看到的评论内容一致,如图 11-5 所示。 图 11-5 Json 数据结果 这时可以确定,此请求对应的接口就是获取商品评论的接口。这样我们就成功捕获到了在上拉刷新的过程中发生的请求和响应内容。
5. 分析
现在分析一下这个请求和响应的详细信息。首先可以回到 Overview 选项卡,上方显示了请求的接口 URL,接着是响应状态 Status Code、请求方式 Method 等,如图 11-6 所示。 图 11-6 监听结果 这个结果和原本在 Web 端用浏览器开发者工具内捕获到的结果形式是类似的。 接下来点击 Contents 选项卡,查看该请求和响应的详情信息。 上半部分显示的是 Request 的信息,下半部分显示的是 Response 的信息。比如针对 Reqeust,我们切换到 Headers 选项卡即可看到该 Request 的 Headers 信息,针对 Response,我们切换到 JSON TEXT 选项卡即可看到该 Response 的 Body 信息,并且该内容已经被格式化,如图 11-7 所示。 图 11-7 监听结果 由于这个请求是 POST 请求,所以我们还需要关心的就是 POST 的表单信息,切换到 Form 选项卡即可查看,如图 11-8 所示。 图 11-8 监听结果 这样我们就成功抓取 App 中的评论接口的请求和响应,并且可以查看 Response 返回的 JSON 数据。 至于其他 App,我们同样可以使用这样的方式来分析。如果我们可以直接分析得到请求的 URL 和参数的规律,直接用程序模拟即可批量抓取。
6. 重发
Charles 还有一个强大功能,它可以将捕获到的请求加以修改并发送修改后的请求。点击上方的修改按钮,左侧列表就多了一个以编辑图标为开头的链接,这就代表此链接对应的请求正在被我们修改,如图 11-9 所示。 图 11-9 编辑页面 我们可以将 Form 中的某个字段移除,比如这里将 partner 字段移除,然后点击 Remove。这时我们已经对原来请求携带的 Form Data 做了修改,然后点击下方的 Execute 按钮即可执行修改后的请求,如图 11-10 所示。 图 11-10 编辑页面 可以发现左侧列表再次出现了接口的请求结果,内容仍然不变,如图 11-11 所示。 图 11-11 重新请求后结果 删除 Form 表单中的 partner 字段并没有带来什么影响,所以这个字段是无关紧要的。 有了这个功能,我们就可以方便地使用 Charles 来做调试,可以通过修改参数、接口等来测试不同请求的响应状态,就可以知道哪些参数是必要的哪些是不必要的,以及参数分别有什么规律,最后得到一个最简单的接口和参数形式以供程序模拟调用使用。
我们以新浪微博为例来实现一个 Cookies 池的搭建过程。Cookies 池中保存了许多新浪微博账号和登录后的 Cookies 信息,并且 Cookies 池还需要定时检测每个 Cookies 的有效性,如果某 Cookies 无效,那就删除该 Cookies 并模拟登录生成新的 Cookies。同时 Cookies 池还需要一个非常重要的接口,即获取随机 Cookies 的接口,Cookies 运行后,我们只需请求该接口,即可随机获得一个 Cookies 并用其爬取。 由此可见,Cookies 池需要有自动生成 Cookies、定时检测 Cookies、提供随机 Cookies 等几大核心功能。
result = self.new_cookies(username, password) # 成功获取 if result.get('status') == 1: cookies = self.process_cookies(result.get('content')) print(' 成功获取到 Cookies', cookies) if self.cookies_db.set(username, json.dumps(cookies)): print(' 成功保存 Cookies') # 密码错误,移除账号 elif result.get('status') == 2: print(result.get('content')) if self.accounts_db.delete(username): print(' 成功删除账号 ') else: print(result.get('content'))
如果要扩展其他站点,只需要实现 new_cookies() 方法即可,然后按此处理规则返回对应的模拟登录结果,比如 1 代表获取成功,2 代表用户名或密码错误。 代码运行之后就会遍历一次尚未生成 Cookies 的账号,模拟登录生成新的 Cookies。
检测模块
我们现在可以用生成模块来生成 Cookies,但还是免不了 Cookies 失效的问题,例如时间太长导致 Cookies 失效,或者 Cookies 使用太频繁导致无法正常请求网页。如果遇到这样的 Cookies,我们肯定不能让它继续保存在数据库里。 所以我们还需要增加一个定时检测模块,它负责遍历池中的所有 Cookies,同时设置好对应的检测链接,我们用一个个 Cookies 去请求这个链接。如果请求成功,或者状态码合法,那么该 Cookies 有效;如果请求失败,或者无法获取正常的数据,比如直接跳回登录页面或者跳到验证页面,那么此 Cookies 无效,我们需要将该 Cookies 从数据库中移除。 此 Cookies 移除之后,刚才所说的生成模块就会检测到 Cookies 的 Hash 和账号的 Hash 相比少了此账号的 Cookies,生成模块就会认为这个账号还没生成 Cookies,那么就会用此账号重新登录,此账号的 Cookies 又被重新更新。 检测模块需要做的就是检测 Cookies 失效,然后将其从数据中移除。 为了实现通用可扩展性,我们首先定义一个检测器的父类,声明一些通用组件,实现如下所示:
test() 方法首先将 Cookies 转化为字典,检测 Cookies 的格式,如果格式不正确,直接将其删除,如果格式没问题,那么就拿此 Cookies 请求被检测的 URL。test() 方法在这里检测微博,检测的 URL 可以是某个 Ajax 接口,为了实现可配置化,我们将测试 URL 也定义成字典,如下所示:
1
TEST_URL_MAP = {'weibo': 'https://m.weibo.cn/'}
如果要扩展其他站点,我们可以统一在字典里添加。对微博来说,我们用 Cookies 去请求目标站点,同时禁止重定向和设置超时时间,得到响应之后检测其返回状态码。如果直接返回 200 状态码,则 Cookies 有效,否则可能遇到了 302 跳转等情况,一般会跳转到登录页面,则 Cookies 已失效。如果 Cookies 失效,我们将其从 Cookies 的 Hash 里移除即可。
接口模块
生成模块和检测模块如果定时运行就可以完成 Cookies 实时检测和更新。但是 Cookies 最终还是需要给爬虫来用,同时一个 Cookies 池可供多个爬虫使用,所以我们还需要定义一个 Web 接口,爬虫访问此接口便可以取到随机的 Cookies。我们采用 Flask 来实现接口的搭建,代码如下所示:
import time from multiprocessing import Process from cookiespool.api import app from cookiespool.config import * from cookiespool.generator import * from cookiespool.tester import *
classScheduler(object): @staticmethod defvalid_cookie(cycle=CYCLE): whileTrue: print('Cookies 检测进程开始运行 ') try: for website, cls in TESTER_MAP.items(): tester = eval(cls + '(website="' + website + '")') tester.run() print('Cookies 检测完成 ') del tester time.sleep(cycle) except Exception as e: print(e.args)
人工智能技术(以下称 AI)是人类优秀的发现和创造之一,它代表着至少几十年的未来。在传统的编程中,工程师将自己的想法和业务变成代码,计算机会根据代码设定的逻辑运行。与之不同的是,AI 使计算机有了「属于自己的思想」,它就像生物一样,能够「看」、「听」、「说」、「动」、「理解」、「分辨」和「思考」。 AI 在图像识别和文本处理方面的效果尤为突出,且已经应用到人类的生活中,例如人脸识别、对话、车牌识别、城市智慧大脑项目中的目标检测和目标分类等。 接下来,我们将了解图像分类的需求、完成任务的前提条件和任务实践。
图像分类以及目标检测的需求
AI 的能力和应用都非常广泛,这里我们主要讨论的是图像分类。 图像分类,其实是对图像中主要目标的识别和归类。例如在很多张随机图片中分辨出哪一张中有直升飞机、哪一张中有狗。或者给定一张图片,让计算机分辨图像中主要目标的类别。 目标检测,指的是检测目标在图片中的位置。例如智慧交通项目中,路面监控摄像头拍摄画面中车辆的位置。目标检测涉及两种技术:分类和定位。也就是说先判定图片中是否存在指定的目标,然后还需要确定目标在图片中的位置。 这样的技术将会应用在人脸识别打卡、视频监控警报、停车场、高速收费站和城市智慧交通等项目当中。
具备以上条件后,再通过短时间(几天或一周)的学习,我们就能够完成图像分类的任务。 讨论个额外的话题,人人都能够做 AI 工程师吗? AI 的门槛是比较高的,首先得具备高等数学、统计学习和编程等基础,然后要有很强的学习能力。对于 IT 工程师来说:
编程基础是没有问题的
学习能力看个人,但花时间、下功夫肯定会有进步
高等数学基础,得好好补
统计学习基础,也得好好补
经济上无压力
如果你想要成为一名 AI 工程师,那么「高学历」几乎是必备的。无论是一线互联网企业或者新崛起的 AI 独角兽,它们为 AI 工程师设立的学历门槛都是「硕士」。除非特别优秀的、才华横溢的大专或本科生,否则是不可能有机会进入这样的企业做 AI 工程师的。 AI 在硬件、软件、数据资料和人才方面都是很费钱的,普通的 IT 工程师也就是学习了解一下,远远达不到产品商用的要求。 普通的中小企业,极少有资质和经济能力吸引高学历且优秀的 AI 工程师,这就导致了资源的聚拢和倾斜。 想要将图像分类技术商用,在让计算机经历「看」、「认识」的步骤并拥有「分辨」能力后,还要将其转换为 Web 服务。 但我只想将人脸识别或者图像分类的功能集成到我的项目当中,就那么困难吗? 我只是一个很小的企业,想要在原来普通的视频监控系统中增加「家人识别」、「陌生人警报」、「火灾警报」和「生物闯入提醒」等功能,没有上述的条件和经济投入,就不能实现了吗? 我好苦恼! 有什么好办法吗?
ModelArts 简介和条件
ModelArts 是华为云推出的产品,它是面向开发者的一站式 AI 开发平台。 它为机器学习与深度学习提供海量数据预处理及半自动化标注、大规模分布式 Training、自动化模型生成,及端-边-云模型按需部署能力,帮助用户快速创建和部署模型,管理全周期 AI 工作流。 它为用户提供了以下可选模式:
零编码经验、零 AI 经验的自动学习模式
有 AI 研发经验的全流程开发模式
同时,它将 AI 开发的整个过程都集成了进来。例如数据标注、模型训练、参数优化、服务部署、开放接口等,这就是「全周期 AI 工作流」。 还有,平台上的操作都是可视化的。 这些条件对于想要将 AI 技术应用于产品,但无奈条件不佳的个人开发者和企业提供了机会,这很重要!可以说 ModelArts) 缩短了 AI 商用的时间,降低了对应的经济成本、时间成本和人力成本。 更贴心的是,华为云 ModelArts) 为用户准备了很多的教程。即使用户没有经验,但只要按照教程指引进行操作,也能够实现自己的 AI 需求。 想想就美滋滋,太棒了! 赶紧体验一下!
# Defaultempty dicts for dict params. data = [] if data is None else data files = [] if files is None else files headers = {}if headers is None else headers params = {}ifparamsis None elseparams hooks = {}if hooks is None else hooks
self.hooks = default_hooks() for (k, v) in list(hooks.items()): self.register_hook(event=k, hook=v)
def schedule(self): """ 调度请求 :return: """ while not self.queue.empty(): weixin_request = self.queue.pop() callback = weixin_request.callback print('Schedule', weixin_request.url) response = self.request(weixin_request) if response and response.status_code in VALID_STATUSES: results = list(callback(response)) if results: for result in results: print('New Result', result) if isinstance(result, WeixinRequest): self.queue.add(result) if isinstance(result, dict): self.mysql.insert('articles', result) else: self.error(weixin_request) else: self.error(weixin_request)
from requests import Session from config import * from db import RedisQueue from mysql import MySQL from request import WeixinRequest from urllib.parse import urlencode import requests from pyquery import PyQuery as pq from requests import ReadTimeout, ConnectionError
ADSL(Asymmetric Digital Subscriber Line,非对称数字用户环路),它的上行和下行带宽不对称,它采用频分复用技术把普通的电话线分成了电话、上行和下行 3 个相对独立的信道,从而避免了相互之间的干扰。 ADSL 通过拨号的方式上网,需要输入 ADSL 账号和密码,每次拨号就更换一个 IP。IP 分布在多个 A 段,如果 IP 都能使用,则意味着 IP 量级可达千万。如果我们将 ADSL 主机作为代理,每隔一段时间主机拨号就换一个 IP,这样可以有效防止 IP 被封禁。另外,主机的稳定性很好,代理响应速度很快。
接下来要做的就是拨号,并把新的 IP 保存到 Redis 散列表里。 首先是拨号定时,它分为定时拨号和非定时拨号两种选择。 非定时拨号:最好的方法就是向该主机发送一个信号,然后主机就启动拨号,但这样做的话,我们首先要搭建一个重新拨号的接口,如搭建一个 Web 接口,请求该接口即进行拨号,但开始拨号之后,此时主机的状态就从在线转为离线,而此时的 Web 接口也就相应失效了,拨号过程无法再连接,拨号之后接口的 IP 也变了,所以我们无法通过接口来方便地控制拨号过程和获取拨号结果,下次拨号还得改变拨号请求接口,所以非定时拨号的开销还是比较大的。 定时拨号:我们只需要在拨号主机上运行定时脚本即可,每隔一段时间拨号一次,更新 IP,然后将 IP 在 Redis 散列表中更新即可,非常简单易用,另外可以适当将拨号频率调高一点,减少短时间内 IP 被封的可能性。 在这里选择定时拨号。 接下来就是获取 IP。获取拨号后的 IP 非常简单,只需要调用 ifconfig 命令,然后解析出对应网卡的 IP 即可。 获取了 IP 之后,我们还需要进行有效性检测。拨号主机可以自己检测,比如可以利用 requests 设置自身的代理请求外网,如果成功,那么证明代理可用,然后再修改 Redis 散列表,更新代理。 需要注意,由于在拨号的间隙拨号主机是离线状态,而此时 Redis 散列表中还存留了上次的代理,一旦这个代理被取用了,该代理是无法使用的。为了避免这个情况,每台主机在拨号之前还需要将自身的代理从 Redis 散列表中移除。 这样基本的流程就理顺了,我们用如下代码实现:
class Sender(): def get_ip(self, ifname=ADSL_IFNAME): """ 获取本机 IP :param ifname: 网卡名称 :return: """ (status, output) = subprocess.getstatusoutput('ifconfig') if status == 0: pattern = re.compile(ifname + '.*?inet.*?(d+.d+.d+.d+).*?netmask', re.S) result = re.search(pattern, output) if result: ip = result.group(1) return ip
目前为止,我们已经成功实时更新拨号主机的代理。不过还缺少一个模块,那就是接口模块。像之前的代理池一样,我们也定义一些接口来获取代理,如 random 获取随机代理、count 获取代理个数等。 我们选用 Tornado 来实现,利用 Tornado 的 Server 模块搭建 Web 接口服务,示例如下:
defget(self, api=''): ifnot api: links = ['random', 'proxies', 'names', 'all', 'count'] self.write('<h4>Welcome to ADSL Proxy API</h4>') for link in links: self.write('<a href=' + link + '>' + link + '</a><br>')
if api == 'random': result = self.redis.random() if result: self.write(result)
if api == 'names': result = self.redis.names() if result: self.write(json.dumps(result))
if api == 'proxies': result = self.redis.proxies() if result: self.write(json.dumps(result))
if api == 'all': result = self.redis.all() if result: self.write(json.dumps(result))
if api == 'count': self.write(str(self.redis.count()))
INFO Deploying: git INFO Clearing .deploy_git folder... INFO Copying files frompublic folder... INFO Copying files from extend dirs... On branch master nothing to commit, working directory clean Counting objects: 46, done. Delta compression using up to 8 threads. Compressing objects: 100% (36/36), done. Writing objects: 100% (46/46), 507.66 KiB | 0 bytes/s, done. Total 46 (delta 3), reused 0 (delta 0) remote: Resolving deltas: 100% (3/3), done. To git@github.com:NightTeam/nightteam.github.io.git * [new branch] HEAD -> master Branch master set up to track remote branch master fromgit@github.com:NightTeam/nightteam.github.io.git. INFO Deploy done: git
# Sidebar Avatar avatar: # In theme directory (source/images): /images/avatar.gif # In site directory (source/uploads): /uploads/avatar.gif # You can also use other linking images. url:/images/avatar.png # If true, the avatar would be dispalyed in circle. rounded:true # If true, the avatar would be rotated with the cursor. rotated:true
作为程序猿,代码块的显示还是需要很讲究的,默认的代码块我个人不是特别喜欢,因此我把代码的颜色修改为黑色,并把复制按钮的样式修改为类似 Mac 的样式,修改 _config.yml 文件的 codeblock 区块如下:
1 2 3 4 5 6 7 8 9 10 11 12
codeblock: # Code Highlight theme # Available values: normal | night | night eighties | night blue | night bright # See: https://github.com/chriskempson/tomorrow-theme highlight_theme:nightbright # Add copy button on codeblock copy_button: enable:true # Show text copy result. show_result:true # Available values: default | flat | mac style:mac
bookmark: enable:false # Customize the color of the bookmark. color:"#222" # If auto, save the reading progress when closing the page or clicking the bookmark-icon. # If manual, only save it by clicking the bookmark-icon. save:auto
# `Follow me on GitHub` banner in the top-right corner. github_banner: enable:true permalink:https://github.com/NightTeam/nightteam.github.io title:NightTeamGitHub
# Multiple Comment System Support comments: # Available values: tabs | buttons style: tabs # Choose a comment system to be displayed by default. # Available values: changyan | disqus | disqusjs | facebook_comments_plugin | gitalk | livere | valine | vkontakte active: gitalk
# Gitalk # Demo: https://gitalk.github.io # For more information: https://github.com/gitalk/gitalk gitalk: enable: true github_id: NightTeam repo: nightteam.github.io # Repository name to store issues client_id: {your client id} # GitHub Application Client ID client_secret: {your client secret} # GitHub Application Client Secret admin_user: germey # GitHub repo owner and collaborators, only these guys can initialize gitHub issues distraction_free_mode: true # Facebook-like distraction free mode # Gitalk's display language depends on user's browser or system environment # If you want everyone visiting your site to see a uniform language, you can set a force language value # Available values: en | es-ES | fr | ru | zh-CN | zh-TW language: zh-CN
可能在一些情况下我们需要写一个公式,比如演示一个算法推导过程,MarkDown 是支持公式显示的,Hexo 的 Next 主题同样是支持的。 Next 主题提供了两个渲染引擎,分别是 mathjax 和 katex,后者相对前者来说渲染速度更快,而且不需要 JavaScript 的额外支持,但后者支持的功能现在还不如前者丰富,具体的对比可以看官方文档:https://theme-next.org/docs/third-party-services/math-equations。 所以我这里选择了 mathjax,通过修改配置即可启用:
1 2 3 4 5 6 7 8 9 10 11 12 13
math: enable:true
# Default (true) will load mathjax / katex script on demand. # That is it only render those page which has `mathjax: true` in Front-matter. # If you set it to false, it will load mathjax / katex srcipt EVERY PAGE. per_page:true
# hexo-renderer-pandoc (or hexo-renderer-kramed) required for full MathJax support. mathjax: enable:true # See: https://mhchem.github.io/MathJax-mhchem/ mhchem:true
# Local search # Dependencies: https://github.com/wzpan/hexo-generator-search local_search: enable:true # If auto, trigger search by changing input. # If manual, trigger search by pressing enter key or search button. trigger:auto # Show top n results per article, show all results by setting to -1 top_n_per_article:5 # Unescape html strings to the readable one. unescape:false # Preload the search data when the page loads. preload:false
Streams are high-level async/await-ready primitives to work with network connections. Streams allow sending and receiving data without using callbacks or low-level protocols and transports.
看到这里,你或许会有另一个疑问:write() 只是将消息写入到 IO 流,并没有发送行为,那消息是如何传输到 Server 的呢? 由于无法直接跟进 CPython 源代码,所以我们无法得到确切的结果。但我们可以跟进 Python 代码,得知消息最后传输到 transport.write() ,如果你想知道更多,可以去看 Transports and Protocols 的介绍。你可以将这个过程抽象为上图的 Client to send buffer to NIC to recv buffer to Server。
如果你无法直接请求成功的话,就可能是 DNS 解析出问题了,可以尝试 ping 一个公网 IP(如:ping 1.1.1.1)和一个域名(如:ping ip.cn),如果 IP 能 ping 通但域名会报 DNS 解析失败的话,就可以确认是 DNS 设置问题了。 4G 拨号时如果出现 DNS 设置问题,通常是因为拨号工具没有正常地将运营商返回的 DNS 服务器设置写入到配置中,我们可以手动配置一下(你要强制指定某一个 DNS 也可以):
这里使用的 /dev/ttyUSB2 是指 4G 网卡的第三个通信端口,文档中的解释为:ttyUSB2→For AT command communication,与第四个通信端口类似,只是它不能用于 PPP 连接、只能用于 AT 命令通信而已。 不同样使用第四个通信端口的原因是那个端口有被占用的可能性,直接区分开最稳妥,本来网卡也就是提供了两个 AT 命令通信渠道的。
最后的最后,我给这个搭建方式打个评价吧。 这个搭建方式并不完美,因为变量太多,而且很多地方肯定不如系统级原生支持的那么稳定,长期使用可能会出现各种奇奇怪怪的问题。 然后 Docker 的资源占用其实挺高的,会浪费相当多的内存在启动容器上,如果只是两三个网卡还好,如果数量大一点的话,像树莓派2B 这种小内存的设备根本就扛不住。 另外代理服务器本身对资源的消耗也是比较高的,高频调用下对树莓派2B 的小 CPU 压力还是蛮大的,即使我对它的 CPU 进行了超频,在并发测试时也还是会出现轻松打满 CPU 的情况。 但是!截止目前,我还有两种基于路由器系统的搭建方案没写出来!所以…敬请期待后续的其他搭建方案(斜眼笑)。
文章作者:「夜幕团队 NightTeam」 - Loco 夜幕团队成立于 2019 年,团队包括崔庆才、周子淇、陈祥安、唐轶飞、冯威、蔡晋、戴煌金、张冶青和韦世东。 涉猎的编程语言包括但不限于 Python、Rust、C++、Go,领域涵盖爬虫、深度学习、服务研发、对象存储等。团队非正亦非邪,只做认为对的事情,请大家小心。
那么怎么来实现呢?要实现上面的功能,其实最重要的就是来识别是哪一个用户,也就是说,我怎么知道到底是谁在浏览我的博客呢?我怎么来专门针对这个用户解锁呢? 有的同学可能说那就用 IP 地址呗,技术角度是可以实现的,但是其实仔细想想,用 IP 地址是很不友好的。一来是很多用户可能都是内网的 IP 地址,多个公户共享一个公网 IP 地址,所以假如两台设备接入了同一个公网 IP,我是无法判断到底解锁哪一台设备的。二来是,如果一个用户换了其他的地方或者用了 VPN,IP 地址变了,原本解锁的设备又变成非解锁状态了。这样也不好。 那么最方便简单的用来标识一个浏览设备的东西是什么?当然是 Cookies。Cookies 里面保存了浏览网页时自动生成的 Session ID,而且每一个用户都是不一样的,这样不就可以来唯一标识一台浏览设备了吗?
解锁逻辑
好,那有了用户的 ID,我怎么才能把用户 ID 和我的公众号关联起来呢?当然是把这个 ID 发到公众号后台,我来存起来就好了。然后博客这边定时检测我这边有没有把这个 ID 保存,如果保存了,那就呈现解锁状态,如果没有保存,那就呈现非解锁状态。 最开始我就设想,既然公众号要扫码关注,那么我能不能把这个 ID 也糅到二维码里面呢?这样关注公众号的时候既能查询到公众号,有传递过来一个 ID 作为参数,然后后台处理一下存起来就好了。 你别说还真有这个功能,我在微信平台官方文档里面查询到了一个「生成带参数的公众号二维码」,生成的二维码里面可以指定任意的参数,然后生成的二维码图案就是公众号的二维码,然后处理一下关注公众号的回调函数就可以执行某一些操作了。看到之后我就想起来了很多关注公众号自动登录的功能就是这么做的。 但是经过一系列操作,发现了一个很悲伤的事情,只有服务号才有这个功能,我一小小的订阅号,是没有这个权限的,不能生产带参数的二维码。哎,难道凉了吗? 不,没有,既然这个参数不能通过二维码传递,那就只好麻烦读者手动把这 ID 输入到我的公众号了,我的小小的订阅号还是有处理消息的功能的。我的公众号后台接收到消息,然后处理下这个消息 ID,然后存起来,那不就好了吗? 说干就干!
这里就是设置个 240 像素的区块,然后从上面到下面是透明度渐变颜色就好了,整体效果是下面这个样子: 好,既然隐藏了,那么下面就加个提示吧,把公众号的二维码先放上,然后把那个 Session ID 放上,提示用户关注公众号后发送这个 ID 就能解锁了,但这个 ID 又不能太长,多少呢?六位吧那就。 类似做成这样的样子: 好,那么这个 ID 怎么获取的呢? 刚才说了,从 Cookies 里面获取就行了,找那个能够标识 Session ID 的一个 Cookies 字段,然后摘取其值的其中几位就行了,摘取的位置也有讲究,前几位仿佛重复率很高的样子,后面几位几乎不重复,那就截取最后六位数字吧。 好,然后我就在博客里面加了这么一点 JavaScript 代码来实现这个 ID 的提取:
1 2 3 4 5 6 7 8 9 10 11 12 13
function getCookie(name) { var value = "; " + document.cookie; var parts = value.split("; " + name + "="); if (parts.length == 2) return parts.pop().split(";").shift(); }
function getToken() { letvalue = getCookie('UM_distinctid') if (!value) { return defaultToken } returnvalue.substring(value.length - 6).toUpperCase() }
这里 getCookie 方法是用某个名字获取一个 Cookies 字段,getToken 方法是截取了 Cookies 这个字段值的后六位并做了大写处理。 这里我的一个可以用来标识 Session ID 的 Cookies 字段叫做 UM_distinctid,就用它了。 这样一来,每个用户浏览的时候就能生成这样的一个 ID 了,六位的。 胜利似乎越来越近了。
持久化存储
这里就又遇到一个问题,刚才不是说还要在用户关闭浏览器之后再重新打开,依然能保持解锁状态吗?这就要求这个 ID 在用户关闭又打开浏览器的时候是不变的。 这个怎么解?很简单,反正已经是从 Cookies 里面读了,这个 Cookies 持久化就行了,只要不在浏览器关闭后清除就行了,怎么办?设置个过期时间就好。 由于我的站点是 WordPress 做的,所以这个功能自动有了,如果没有的话用一些插件也能实现的。
公众号处理
好,现在 ID 也有了,用户扫码把这个 ID 发到公众号后台就行了吧,然后公众号对接开发者模式处理一下就好了。 这里就其实就很简单了,其实仅仅就是把用户的 OpenID 和这个码存到了一个数据库里面。我后台是用 Django 写的,所以用了 Django 里面的 Model,实现逻辑如下:
把这个方法对接一个 API 接口,比如 /api/locked?token=xxxxx,就可以知道是否解锁了。 所以,在公众号后台我就用开发者模式对接了这么两个功能,一个用来存,一个用来查。只要用户发送了这个能够用来表示自己浏览设备的码,我就存下来,然后博客定时请求这个 API 查询状态,如果返回结果是未解锁状态,那就继续锁住,如果是解锁状态。那就把博客解开。
(articleSelector).ready(function () { var articleElement = $(articleSelector)[0] if (articleElement) { var height = articleElement.clientHeight var halfHeight = height * 0.3 var token = getToken() $('#locker').find('.token').text(token) functiondetect() { $.ajax({ url: 'https://weixin.cuiqingcai.com/api/locked/', method: 'GET', data: { token: token }, success: function (data) { if (data.locked === true || data.locked === false) { locked = data.locked } }, error: function (data) { locked = false } }) } } })
这里就用基本的 jQuery 实现的,其实就是调了个 Ajax,也没啥高深的技巧。这里唯一值得注意的一点设计就是,如果 API 请求失败,这基本上证明我的 API 服务挂掉了,这里就需要把 locked 设置为 false,证明为解锁状态。这样,万一我的 API 后台挂了,博客会直接是解锁状态,这样就避免了读者永远无法解锁了。这是一个细节上的设计。 至此,一些技术上的问题就基本解决了。
手机端处理
最后回过头来看看,那个需求还没有满足? 读者在手机或其他移动设备上不方便操作,手机站点禁止启用本功能。那么怎么实现呢?很简单,判断一下浏览器的 User-Agent 就好了,这里实现了一个判断是否是 PC 的方法:
基于网页文档的提取方法将 HTML 文档视为文本进行处理,适用于处理含有大量文本信息且结构简单易于处理的单记录网页,或者具有实时要求的在线分析网页应用。 这种方式主要利用自然语言处理相关技术实现,通过理解 文本语义、分析上下文、设定提取规则等,实现对大段网页文档的快速处理。其中,较为知名的方法有 TSIMMIS、Web-OQL、Serrano、FAR-SW 和 FOREST,但这些方法由于通常需要人工的参与,且存在耗时长、效率低的弊端。 基于 DOM 结构信息的方法将 HTML 文档解析为相应的 DOM 树,然后根据 DOM 树的语法结构创建提取规则, 相对于以前的方法而言有了更高的性能和准确率。 W4F 和 XWRAP 将 HTML 文档解析成 DOM 树,然后通过组件化引导用户通过人工选择或者标记生成目标包装器代码。Omini、IEPAD 和 ITE 提取 DOM 树上的关键路径, 获取其中存在的重复模式。MDR 和 DEPTA 挖掘了页面中的数据区域,得到数据记录的模式。CECWS 通过聚类算法从数据库中提取出自同一网站的一组页面,并进行 DOM 树结构的对比,删除其中的静态部分,保留动态内容作为信息提取的结果。虽然此类方法相对于上一类方法 具有较高的提取精度,且克服了对大段连续文本的依赖, 但由于网页的 DOM 树通常较深,含有大量 DOM 节点, 因此基于 DOM 结构信息的方法具有较高的时间和空间消耗。目前来说,大部分原理还是基于 DOM 节点的文本密度、标点符号密度等计算的,其准确率还是比较可观的。今天所介绍的 Readability 和 Newspaper 的库的实现原理就是类似。 目前比较先进的是基于视觉信息的网页信息提取方法,通过浏览器接口或者内核对目标网页预渲染,然后基于网页的视觉规律提取网页数据记录。经典的 VIPS 算法首先从 DOM 树中提取出所有合适的页面区域,然后根据这些页面和分割条重新构建 Web 页面的语义结构。作为对 VIPS 的拓展,ViNT、ViPER、ViDE 也成功利用了网页的视觉特征来实现数据提取。CMDR 为通过神经网络学习多记录型页面中的特征,结合基于 DOM 结构信息的 MDR 方法,挖掘社区论坛页面的数据区域。与上述方法不同,VIBS 将图像领域的 CNN 卷积神经网络运用于网页的截图,同时通过类 VIPS 算法生成视觉块,最后结合两个阶段的结果识别网页的正文区域。另外还有最新的国内提出的 VBIE 方法,基于网页视觉的基础上改进,可以实现无监督的网页信息提取。
authors: ['Cnn Wire'] date: 2013-12-30 00:00:00 text: By Leigh Ann Caldwell
WASHINGTON (CNN) — Not everyone subscribes to a New Year’s resolution, but Americans will be required to follow new laws in 2014.
Some 40,000 measures taking effect range from sweeping, national mandates under Obamacare to marijuana legalization in Colorado, drone prohibition in Illinois and transgender protections in California.
Although many new laws are controversial, they made it through legislatures, public referendum or city councils and represent the shifting composition of American beliefs. ... ... Colorado: Marijuana becomes legal in the state for buyers over 21 at a licensed retail dispensary.
(Sourcing: much of this list was obtained from the National Conference of State Legislatures). top image: https://localtvkstu.files.wordpress.com/2012/04/national-news-e1486938949489.jpg?quality=85&strip=all movies: [] keywords: ['drones', 'national', 'guns', 'wage', 'law', 'pot', 'leave', 'family', 'states', 'state', 'latest', 'obamacare', 'minimum', 'laws'] summary: Oregon: Family leave in Oregon has been expanded to allow eligible employees two weeks of paid leave to handle the death of a family member. Arkansas: The state becomes the latest state requiring voters show a picture ID at the voting booth. Minimum wage and former felon employmentWorkers in 13 states and four cities will see increases to the minimum wage. New Jersey residents voted to raise the state’s minimum wage by $1 to $8.25 per hour. California is also raising its minimum wage to $9 per hour, but workers must wait until July to see the addition.
Building prefix dict from /usr/local/lib/python3.7/site-packages/jieba/dict.txt ... Dumping model to file cache /var/folders/1g/l2xlw12x6rncs2p9kh5swpmw0000gn/T/jieba.cache Loading model cost 1.7178938388824463 seconds. Prefix dict has been built succesfully. authors: [] title: 今年iPhone只有小改进?分析师:还有其他亮点 date:2019-09-0908:10:26+08:00 text: (原标题:Apple Bets More Cameras Can Keep iPhone Humming)
有人说,这个技术不是什么好技术。万一有人拿着我们的照片一变脸,就能够把我们任意的表情和头部动作模拟出来,拿着去做认证,比如刷脸支付什么的咋办,那我们的钱不就被盗刷了吗? 对于这个问题,支付宝官方也做了回应,支付宝称刷脸支付实际上会通过软硬件结合的方式进行检测,其会判断被刷物体是否是照片、视频或者软件模拟的方式生成的,可以有效避免身份冒用情况。其中有一个核心技术就是通过 3D 结构光摄像头来进行信息采集和识别,如果被拍摄物体是平面的,也就是说如果是照片或者视频,是无法通过检测的。 这时候我自然而然想到,既然用的是 3D 结构光摄像头,那么如果用了 3D 打印技术把一个人的肖像打印出来,或者用一个非常逼真的蜡像来进行刷脸识别,能不能通过呢?我看了一些报道,发现不少案例的确通过了刷脸测试,比如解开了 iPhone 面部识别锁等等。但要通过 3D 打印技术来模拟一个人的肖像成本还是蛮高的,所以基本上也不太会有人来搞这些。 如果对此还心有余悸的话,支付宝还回应称,即便是真的被盗刷了,支付宝也会通过保险公司进行全额赔付。 所以基本上是不用担心其安全性的,尤其是 Zao 这个软件的出现是没有对刷脸支付的风险造成大的影响的,其就是增加了一个活体视频模拟的实现,对刷脸支付的安全性没有出现大的突破性威胁。
用户上传发布内容后,意味着同意授予 ZAO 及其关联公司以及 ZAO 用户在“全球范围内完全免费、不可撤销、永久、可转授权和可再许可的权利”,“包括但不限于可以对用户内容进行全部或部分的修改与编辑(如将短视频中的人脸或者声音换成另一个人的人脸或者声音等)以及对修改前后的用户内容进行信息网络传播以及《著作权法》规定的由著作权人享有的全部著作财产权利及邻接权利”。
这条款没人说还真没注意到,因为一般咱用一个软件,一般不会去仔细看它的条款,那么密密麻麻的一坨,有几个人会去仔细看呢?但要不同意,这个软件还没法用,所以用过这个软件的人,这个条款一定是已经同意了。 这条条款其实是很过分的,同意授予 Zao 及其关联公司以及 Zao 用户在“全球范围内完全免费、不可撤销、永久、可转授权和可再许可的权利。注意这里有几个字,完全免费、不可撤销、永久、可转授权、可再许可,这几个词就代表我们已经把我们的肖像权永久授予了 Zao 及其关联公司了,而且不能撤销,账号注销了也不能撤销,也就是以后它们可以有权利永久滥用我们的肖像。更可怕的是,其中还有一个词叫可转授权,那也就是说,Zao 可以对我们的肖像权进行转授权,你懂得,给点钱,啥办不到呢?这就更无法控制了,这可能就意味着,世界上任何一个人可能都能获得我们的肖像权。 所以说,如果你还没用的话,一定要谨慎谨慎再谨慎! 哎,反正我已经同意了,貌似我现在也没什么办法了。
社会影响
这个软件的出现,更深一点想,其实它所隐含的影响还是蛮大的。 有了这个变脸技术,如果有人获得了我们在条款里面所”捐出“的肖像权,拿着我们的照片去生产那种你懂得的影片,把视频里面的男主或者女主换成我们的人脸,然后到处传播,或者以此作为敲诈勒索的工具。即便我们有理,那也说不清了,首先这个条款已经说了它们可以有权利随意使用我们的肖像,所以告侵犯肖像权已经行不通了,而且即使我们有证据证明这是假的,但这种视频的传播也一定会带来非常大的影响。 按照现在大众们的观念,比如说一张图,我们如果不信的话可以说它是 P 的,但如果换做是视频的话,很多人可能就会相信了,因为很多人不知道视频中的肖像也可以伪造得这么真了,毕竟很多人并不知道这种技术。因此,有了这种技术的出现,以后视频类的证据,可能也不可信了。因此这个软件的出现,可以说从另一个侧面昭示,以后视频也不能作为犯案的证据和验证人的真伪的依据了。 所以以后可能是这样子的:
坐在电脑面前的网络女主播,即便不开美颜和滤镜,你所看到的她也不是真的她了。
你要给人打个钱,说开个视频吧,我看看是不是真的你,即便看到的是他,你也不能信了。
有人要 Qiao Zha 你,把你的人脸换成 Zuo An 分子的脸,你到哪里说理去?
某一天,你作为男女主角,出现在了 P 站和 91….
我一开始想的还没这么深,边想边写,写到这,我自己都开始后怕了… 怎么甚至感觉,以后的社会可能会乱套了呢?这可能就是 AI 发展的一个隐患吧。 所以写到最后,虽然这个软件很有意思,但还是劝大家还没有用的就不要用了吧,真的很可怕。同时我也不知道这个软件这样的条款和做法会不会有什么问题,但还是希望能引起有关部门的注意。 以后,也希望大家也可以在使用软件的时候,要更加谨慎和小心,有条款就稍微看一看,尤其是对于这种和用户隐私相关的软件,要更加心存戒备。
name: Nicholas Wilson address: 70561 Simmons Road Apt. 893 Lake Raymondville, HI 35240 text: Bothbegin bring federal space. Official start idea specific. Able under young fire. Who showline traditional easy people. Until economic lead event case. Technology college his director style.
[<faker.providers.user_agent.Providerobject at 0x10249de48>, <faker.providers.ssn.zh_CN.Providerobject at 0x10249dc18>, <faker.providers.python.Providerobject at 0x10249dd68>, <faker.providers.profile.Providerobject at 0x10249dcc0>, <faker.providers.phone_number.zh_CN.Providerobject at 0x10249dc88>, <faker.providers.person.zh_CN.Providerobject at 0x10249de80>, <faker.providers.misc.Providerobject at 0x10249df60>, <faker.providers.lorem.zh_CN.Providerobject at 0x10249dc50>, <faker.providers.job.zh_CN.Providerobject at 0x10249de10>, <faker.providers.isbn.Providerobject at 0x10249c6d8>, <faker.providers.internet.zh_CN.Providerobject at 0x10249c828>, <faker.providers.geo.en_US.Providerobject at 0x102484748>, <faker.providers.file.Providerobject at 0x102484828>, <faker.providers.date_time.en_US.Providerobject at 0x1023789e8>, <faker.providers.currency.Providerobject at 0x102484780>, <faker.providers.credit_card.Providerobject at 0x1024845f8>, <faker.providers.company.zh_CN.Providerobject at 0x102499ef0>, <faker.providers.color.en_US.Providerobject at 0x1023532e8>, <faker.providers.barcode.Providerobject at 0x101cb6d30>, <faker.providers.bank.en_GB.Providerobject at 0x102378f98>, <faker.providers.automotive.en_US.Providerobject at 0x1017a5c50>, <faker.providers.address.zh_CN.Providerobject at 0x101787c18>]
Expected type 'int', got 'float' instead This inspection detects type errors in function call expressions. Due to dynamic dispatch and duck typing, this is possible in a limited but useful number of cases. Types of function parameters can be specified in docstrings orin Python 3 function annotations.
另外也有一些库是支持类型检查的,比如 mypy,安装之后,利用 mypy 即可检查出 Python 脚本中不符合类型注解的调用情况。 上面只是用一个简单的 int 类型做了实例,下面我们再看下一些相对复杂的数据结构,例如列表、元组、字典等类型怎么样来声明。 可想而知了,列表用 list 表示,元组用 tuple 表示,字典用 dict 来表示,那么很自然地,在声明的时候我们就很自然地写成这样了:
这里可以看到主要用到的类型有 Set、Union、List、MutableMapping、Optional,这些在上文都已经做了解释,另外这里使用了多次 Union 来声明了一些新的类型,如 _Find 则要么是是 Element 对象的列表,要么是单个 Element 对象,_Result 则要么是 Result 对象的列表,要么是单个 Result 对象。另外 _Attrs 其实就是字典类型,这里用 MutableMapping 来表示了,没有用 Dict,也没有用 Mapping。 接下来再看一个 Element 类的声明:
classElement(BaseParser): """An element of HTML. :param element: The element from which to base the parsing upon. :param url: The URL from which the HTML originated, used for ``absolute_links``. :param default_encoding: Which encoding to default to. """
def__repr__(self) -> str: attrs = ['{}={}'.format(attr, repr(self.attrs[attr])) for attr in self.attrs] return"<Element {} {}>".format(repr(self.element.tag), ' '.join(attrs))
@property defattrs(self) -> _Attrs: """Returns a dictionary of the attributes of the :class:`Element <Element>` (`learn more <https://www.w3schools.com/tags/ref_attributes.asp>`_). """ if self._attrs isNone: self._attrs = {k: v for k, v in self.element.items()}
# Split class and rel up, as there are ussually many of them: for attr in ['class', 'rel']: if attr in self._attrs: self._attrs[attr] = tuple(self._attrs[attr].split())
我们在上一节了解了代理的设置方法,利用代理我们可以解决目标网站封 IP 的问题,而在网上又有大量公开的免费代理,其中有一部分可以拿来使用,或者我们也可以购买付费的代理 IP,价格也不贵。但是不论是免费的还是付费的,都不能保证它们每一个都是可用的,毕竟可能其他人也可能在用此 IP 爬取同样的目标站点而被封禁,或者代理服务器突然出故障或网络繁忙。一旦我们选用了一个不可用的代理,势必会影响我们爬虫的工作效率。 所以说,在用代理时,我们需要提前做一下筛选,将不可用的代理剔除掉,保留下可用代理,接下来在获取代理时从可用代理里面取出直接使用就好了。 所以本节我们来搭建一个高效易用的代理池。
接口模块需要用 API 来提供对外服务的接口,其实我们可以直接连数据库来取,但是这样就需要知道数据库的连接信息,不太安全,而且需要配置连接,所以一个比较安全和方便的方式就是提供一个 Web API 接口,通过访问接口即可拿到可用代理。另外由于可用代理可能有多个,我们可以提供随机返回一个可用代理的接口,这样保证每个可用代理都可以取到,实现负载均衡。
defrun(self): print('获取器开始执行') ifnot self.is_over_threshold(): for callback_label in range(self.crawler.__CrawlFuncCount__): callback = self.crawler.__CrawlFunc__[callback_label] proxies = self.crawler.get_proxies(callback) for proxy in proxies: self.redis.add(proxy)
我们在做爬虫的过程中经常会遇到这样的情况,最初爬虫正常运行,正常抓取数据,一切看起来都是那么的美好,然而一杯茶的功夫可能就会出现错误,比如 403 Forbidden,这时候打开网页一看,可能会看到“您的 IP 访问频率太高”这样的提示,或者跳出一个验证码让我们输入,输入之后才可能解封,但是输入之后过一会儿就又这样了。 出现这样的现象的原因是网站采取了一些反爬虫的措施,比如服务器会检测某个 IP 在单位时间内的请求次数,如果超过了这个阈值,那么会直接拒绝服务,返回一些错误信息,这种情况可以称之为封 IP,于是乎就成功把我们的爬虫禁掉了。 既然服务器检测的是某个 IP 单位时间的请求次数,那么我们借助某种方式来伪装我们的 IP,让服务器识别不出是由我们本机发起的请求,不就可以成功防止封 IP 了吗? 所以这时候代理就派上用场了,本章我们会详细介绍一下代理的基本知识及各种代理的使用方式,帮助爬虫脱离封 IP 的苦海。 本章接下来会介绍代理的设置、代理池的维护、付费代理的使用、ADSL拨号代理的搭建方法。
import time from io import BytesIO from PIL import Image from selenium import webdriver from selenium.common.exceptions import TimeoutException from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC
defget_points(self, captcha_result): """ 解析识别结果 :param captcha_result: 识别结果 :return: 转化后的结果 """ groups = captcha_result.get('pic_str').split('|') locations = [[int(number) for number in group.split(',')] for group in groups] return locations
deftouch_click_words(self, locations): """ 点击验证图片 :param locations: 点击位置 :return: None """ for location in locations: print(location) ActionChains(self.browser).move_to_element_with_offset(self.get_touclick_element(), location[0], location[1]).click().perform() time.sleep(1)
defget_gap(self, image1, image2): """ 获取缺口偏移量 :param image1: 不带缺口图片 :param image2: 带缺口图片 :return: """ left = 60 for i in range(left, image1.size[0]): for j in range(image1.size[1]): ifnot self.is_pixel_equal(image1, image2, i, j): left = i return left return left
模拟拖动的这个过程说复杂并不复杂,只是其中的坑比较多。现在我们已经获取到了缺口的位置,接下来只需要调用拖动的相关函数将滑块拖动到对应位置不就好了吗?然而事实很残酷,如果匀速拖动,极验必然会识别出来这是程序的操作,因为人是无法做到完全匀速拖动的,极验利用机器学习模型筛选出此类数据,归类为机器操作,验证码识别失败。 随后我又尝试了分段模拟,将拖动过程划分几段,每段设置一个平均速度,同时速度围绕该平均速度小幅度随机抖动,同样无法完成验证。 最后尝试了完全模拟加速减速的过程通过了验证,在前段滑块需要做匀加速运动,后面需要做匀减速运动,在这里利用物理学的加速度公式即可完成。 设滑块滑动的加速度用 a 来表示,当前速度用 v 表示,初速度用 v0 表示,位移用 x 表示,所需时间用 t 表示,则它们之间满足如下关系:
while current < distance: if current < mid: # 加速度为正2 a = 2 else: # 加速度为负3 a = -3 # 初速度v0 v0 = v # 当前速度v = v0 + at v = v0 + a * t # 移动距离x = v0t + 1/2 * a * t^2 move = v0 * t + 1 / 2 * a * t * t # 当前位移 current += move # 加入轨迹 track.append(round(move)) return track
文档中有介绍,它以 os.getcwd() 的形式将路径返回。我们去源码中一探究竟(Pycharm 编辑器快捷键 ctrl+鼠标左键点击即可跟进指定对象) 原来它是对 os 模块中一些对象进行了封装,看 cwd 的注释: Return a new path pointing to the current working directory 意为:返回指向当前工作目录的新路径。 看起来也没什么特别的,但是为什么官方特意将它推出呢?
Pure paths are useful in some special cases; for example: If you want to manipulate Windows paths on a Unix machine (or vice versa). You cannot instantiate a WindowsPath when running on Unix, but you can instantiate PureWindowsPath. You want to make sure that your code only manipulates paths without actually accessing the OS. In this case, instantiating one of the pure classes may be useful since those simply don’t have any OS-accessing operations. 翻译:纯路径在某些特殊情况下很有用; 例如: 如果要在Unix计算机上操作Windows路径(反之亦然)。WindowsPath在Unix上运行时无法实例化,但可以实例化PureWindowsPath。 您希望确保您的代码仅操作路径而不实际访问操作系统。在这种情况下,实例化其中一个纯类可能很有用,因为那些只是没有任何操作系统访问操作。
还附上了一张图: 一下子也不是很理解,这是什么意思。不要紧,继续往下看。
对应关系
通过以上的例子我们可以感受到,它不仅封装了 os.path 相关常用方法,还集成了 os 的其他模块,比如创建文件夹 Path.mkdir。 如果你担心记不住,没关系的,文档一直都在。并且文档给我们列出了对应关系表
This module offers classes representing filesystem paths with semantics appropriate for different operating systems. Path classes are divided between pure paths, which provide purely computational operations without I/O, and concrete paths, which inherit from pure paths but also provide I/O operations. 释义:pathlib 提供表示文件系统路径的类,其语义适用于不同的操作系统。路径类在纯路径之间划分,纯路径提供纯粹的计算操作而没有I / O,以及具体路径,它继承纯路径但也提供I / O操作。
回顾刚才这张图,重新理解 pathlib 如果你以前从未使用过这个模块,或者只是不确定哪个类适合您的任务,那么Path很可能就是您所需要的。它为代码运行的平台实例化一个具体路径。 总结:pathlib 不单纯是对 os 中一些模块或方法进行封装,而是为了兼容不同的操作系统,它为每类操作系统定义了接口。你希望在UNIX机器上操作Windows的路径,然而直接操作是做不到的,所以为你创建了一套接口 PurePath,你可以通过接口来实现你的目的(反之亦然)