药监局可以查询每个药企的许可证编号。当我们如果想查找多家药企的许可证编号时候，就必须一条条查询，很麻烦。所以就需要想办法使用自动化程序来帮我们处理。后面当自动化程序运行起来后，发现这个网站是有防D（DDOS防御）的。而且频率限制的相当低。通常这种有两个解决办法。拉长每条数据爬取间隔，这样显然会让爬取数据的效率大大降低。通过动态代理IP来实现绕过防D检测，这样效率会非常高，效率无上限。但是要找到好用干净的IP还是比较难的。我在接下来测试时候，还交了40元学费后发现这些地址全部被药监局封了。白瞎了这40块钱。声明：本文章中所有内容仅供学习交流，抓包内容、敏感网址、数据接口均已做脱敏处理，严禁用于商业用途和非法用途，否则由此产生的一切后果均与作者无关，若有侵权，请联系我立即删除！什么是爬虫接触到现在，也许很多人眼里，爬虫就是获取网页数据的。自然也把实现这一目标的都归为爬虫。这么理解也没问题。那实现这一目的的有哪些方式呢？通过request请求直接发送请求获取response响应结果。特点效率高，难度高。（单子价格高，通常一个网页的爬虫通过此方式做出来，价格在几百到上万不等）自动化测试工具selenium、drissionpage 实现自动对浏览器的操作。特点效率慢，难度低。按我理解request才是真正的爬虫（直接交互数据），而后者selenium、drissionpage只是自动化测试工具。网站分析查看请求包打开浏览器后点击F12调试，再刷新下网页就能够看到所有的http请求。如上图，刚调试就遇到了两个问题：无限被Debug，正常刚打开调试不会进入Debug。后来关闭Debug，点击左侧三角箭头继续运行才加载出来查询结果。发生卡死现象，当想把一个个改成跳过Debug，可能触发了什么逻辑，直接网页卡死。后来也是按如上操作才出现了下面的请求结构。接下去我们来分析一下。网页上其实一个选择药企类型，一个是输入药企名称。然后点击查询按钮返回结果。正常的开发逻辑比较像上图的请求。同时我们看到药企的类型和名称全部是加密的，在header中还有sign签名。需要分析三块地方的的加密方法甚至更多。再加上无限Debug。对我爬虫初学者来说的确有点棘手，所以对于这种效率要求不高的情况下，使用自动化测试工具是个不错的选择。自动化测试工具当我使用selenium来访问药监局网站时候发现无法打开。为什么手动可以打开，用selenium无法打开？在我们手动直接访问、selenium、drissionpage访问有什么区别呢？访问https://bot.sannysoft.com/测试看下。手动直接访问浏览器特征全部通过。selenium会被检测到webdriver属性。测试药监局网站时候，由于被检测到webdriver属性，则直接显示白屏。from selenium import webdriver import time driver = webdriver.Chrome() driver.get("https://bot.sannysoft.com/") time.sleep(60) driver.quit()drissionpage和手动访问完全一致 ，浏览器特征全部通过。import time from DrissionPage import ChromiumPage page = ChromiumPage() page.get("https://bot.sannysoft.com/") time.sleep(60) page.quit()编写代码自动化测试工具本质还是模拟浏览器中点击输入等等操作。最常见的无非就是点击、输入、新建和切换标签页。想具体了解可以看drissonpage官方文档。下面就以官网文档来演示，如何把这些操作，通过代码来实现。准备工作安装Python V3.10以上版本pip安装软件包 pip install drissionpage安装chrome浏览器打开网页将上面的page.get("https://bot.sannysoft.com/") 网址替换为https://www.drissionpage.cn/ChromiumPage/tab此代码运行后就是打开网页的操作。定位元素通常元素定位有三个方式，一种是通过html元素选择器来定位，第二种种则是通过元素标签路径来定位，第三种元素标签来定位。如上图，我们要定位到右上角搜索框，那就在搜索框位置右键-->检查。弹出调试控制台，直接会到达该元素所在位置。其元素class选择器名称为DocSearch-Button-Placeholder，且搜索后该页面只有一个。（id选择器全局唯一，class选择器是有可能名称重复的，所以需要搜索确认下是否只有一个）。而sapn、div、button位于每行开头处的是标签。元素选择器page.ele('@class=DocSearch-Button-Placeholder').click() #.click 是触发点击动作，.click之前则是定位元素。元素标签路径page.ele( '/html/body/div[1]/div/div/header/form/input').input('标签页操作') #搜索「标签页操作」相关内容 page.ele('#docsearch-item-1').click() #和css语法类似，使用id选择器时候需要用#开头。点击第一个搜索到的内容元素标签通过标签来定位元素用的不多，因为标签名称重复的太多了。默认都是最靠上的。通过标签定位的话需要层层递进。# 此处代码和当前网页无关 tb_ele = page.ele('tag:tbody') #定位到表单元素 tr_eles = tb_ele.eles('tag:tr') #定位到所有行元素、ele是定位到一个元素。定位多个元素为列表时候使用eles for tr in tr_eles: #循环对其中元素操作 name = tr.ele('@class=el-table_1_column_2 is-center ').text输入内容查找page.ele( '/html/body/div[1]/div/div/header/form/input').input('标签页操作') 语法基本都一样前面是定位，后面是动作。input就是输入内容的动作。获取文本name = page.ele('@class=DocSearch-Hit-source').text #text是获取文字元素的内容，并赋值给name变量 print(name) #打印文字内容使用代理drissionpage包自带drissionpage 包直接支持的代理缺点就是只能在创建ChromiumPage实例之前设置。当开始自动执行后无法切换代理。代码如下。from DrissionPage import ChromiumPage,DriverOptions options = DriverOptions(read_file=False) proxy= '112.250.249.60:38072' options.set_proxy(proxy) page = ChromiumPage(addr_driver_opts=options) page.get('https://www.baidu.com/') print(page.html)浏览器插件通过proxy_switchyomega浏览器插件实现代理。怎么说呢，被防D了，降低点速度好了，如果说通过代理来切换ip，来破DDOS防御，达到提速效果的话，我感觉是多虑了。首先通过每个ip爬取5条数据后换一个ip来访问，这样做的确能提高一定爬取数据速度没错。但这毕竟是自动化测试工具，就算这么做最多也就提升个五六倍吧，或者再加10个浏览器实例一起跑。那速度也就从原来7秒一条数据，上升到1秒10条数据。这种方式吃自己电脑资源不说，速度提升上也差强人意。通过request直接构造请求数据包，再通过设置代理，直接发后端，一秒钟少说也能达到几百上千。不过也放一下代码吧。import platform from DrissionPage import ChromiumPage, ChromiumOptions from loguru import logger import requests headers = { 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7', 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,en-US;q=0.7,en-GB;q=0.6', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive', 'DNT': '1', 'Pragma': 'no-cache', 'Upgrade-Insecure-Requests': '1', 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36', } params = { 'count': '5', 'neek': '112095', 'type': '1', 'sep': '4', 'sb': '', 'ip_si': '1', 'mr': '0', } response = requests.get('http://tiqu.pyhttp.taolop.com/getflowip', params=params, headers=headers, verify=False) def get_free_ip(): url = "https://www.zdaye.com/free/?ip=&adr=&checktime=&sleep=&cunhuo=&dengji=1&nadr=&https=&yys=&post=&px=" browser.get(url, retry=3, interval=1, timeout=15) ip_ports = [] for tr in browser.eles('x://table[@id="ipc"]//tr')[1:]: tds = [td.text for td in tr.eles("x://td")] ip_ports.append((f"{tds[0]}:{tds[1]}", tds[3])) print(len(ip_ports), ip_ports) return ip_ports def switch_ip(ip_port=None): global set_proxy if ip_port: # 设置proxy ip, port = ip_port.split(":") tab = browser.new_tab() tab.get( "chrome-extension://padekgcemlokbadohgkifijomclgjgif/options.html#!/profile/proxy") tab.ele( 'x://input[@ng-model="proxyEditors[scheme].host"]').input(ip, clear=True) tab.ele( 'x://input[@ng-model="proxyEditors[scheme].port"]').input(port, clear=True) tab.ele('x://a[@ng-click="applyOptions()"]').click() tab.wait(1) # 提示框 txt = tab.handle_alert() print("提示框", txt) tab.handle_alert(accept=False) if not omega_proxy: # 切换proxy tab.get( "chrome-extension://padekgcemlokbadohgkifijomclgjgif/popup/index.html#") tab.wait(1) tab.ele('x://span[text()="proxy"]').click() set_proxy = True else: tab = browser.new_tab() tab.get("chrome-extension://padekgcemlokbadohgkifijomclgjgif/popup/index.html#") tab.ele('x://span[text()="[直接连接]"]').click() if len(browser.tab_ids) > 1: print("当前tab个数", len(browser.tab_ids)) tab.close() if platform.system().lower() == 'windows': # .set_paths(browser_path=r"C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe") co = ChromiumOptions() else: co = ChromiumOptions().set_paths(browser_path=r"/opt/google/chrome/google-chrome") co.headless(True) # 设置无头加载 无头模式是一种在浏览器没有界面的情况下运行的模式，它可以提高浏览器的性能和加载速 # co.incognito(True) # 无痕隐身模式打开的话，不会记住你的网站账号密码的 # 禁用沙箱 禁用沙箱可以避免浏览器在加载页面时进行安全检查,从而提高加载速度 默认情况下，所有Chrome 用户都启用了隐私沙盒选项 https://zhuanlan.zhihu.com/p/475639754 co.set_argument('--no-sandbox') co.set_argument("--disable-gpu") # 禁用GPU加速可以避免浏览器在加载页面时使用过多的计算资源，从而提高加载速度 co.set_user_agent( user_agent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36') # 设置ua co.set_timeouts(6, 6, 6) co.set_local_port(9211) # 1、设置switchyOmega插件 co.add_extension(r'C:\Users\Ad\Desktop\proxy_switchyomega-2.5.20') browser = ChromiumPage(co) # 2、重置switchyOmega插件 omega_proxy = False switch_ip() browser.get("https://www.ip138.com/", retry=0) html_text = browser.get_frame( 'x://div[@class="hd"]//iframe').ele('text:您的iP地址是').text logger.success(f">>>>当前的ip {html_text}") # 3、随机切换代理ip # ip_all = get_free_ip() ip_all = [{"ip": "10.1.3.56", "port": 7890, "expire_time": "2024-04-27 22:24:00"}] for ips in ip_all: logger.info(f"~~~切换ip，now {ips['ip']}") # 重置switchyOmega插件 switch_ip(f"{ips['ip']}:{ips['port']}") browser.wait(1) try: browser.get("https://www.baidu.com/", retry=0) browser.get("https://www.ip138.com/", retry=0) browser.get("https://www.google.com/", retry=0) html_text = browser.get_frame( 'x://div[@class="hd"]//iframe').ele('text:您的iP地址是').text logger.success(f">>>>>>>>切换代理成功 {html_text}") except Exception as err: logger.error(f"----------切换代理失败 dp {err}") browser.wait(10) browser.quit()
日常平台账号的开通是我上班内容之一。记得之前有人抱怨说，为啥给我的密码不对啊。当然不可能是我开错，因为一个开账号的过程简直要把人逼疯，我就说我这边的过程部分（1、从工单系统把账号需求填入表格记录后续用于迎检。 2、打开堡垒机输入账号密码验证码 3、打开平台页面登录 4、创建对应账号、授权、分组 5、复制出初始密码回填表格记录 6、编辑邮件反馈对应人员 7、工单系统反馈）。整整7步，其中最费时间的是3、4、5步骤随着账号数量，这是随账号数量线性上升的工作量。所以我肯定用脚本去解决，当然不可能是我开错或者复制错。那这个问题到底怎么来的呢，会是用户输错么？下面我们就探究一下原因。Unicode编码简介在计算机初期只有ASCII码，最多只有8位，只能够表示256个字符，除了容纳英文字符和几个简单的符号无法容纳更多。随着各国开始使用计算机，为了融入各国的文字。就诞生出了很多编码，如中国用的GBK编码。但是这就引来另一个问题，这些编码在传输的时候两边如果编码不同也没做对应转换的话，那到对端很可能变成真正的天书。所以为解决这个问题就有了Unicode国际组织制定的可以容纳世界上所有文字和符号的字符编码方案。其中UTF-8可变长（使用英文只需要1字节，使用特殊字符需要2字节或者3字节等）的编码就是Unicode编码的实现方式。数据库中经常见到的utf8mb4也是指uft8 most byte 4，最高到4字节，mb3则是最高到3字节。对应如下表。Unicode符号范围 (十六进制)UTF-8编码方式 （二进制）0000 0000-0000 007F0xxxxxxx0000 0080-0000 07FF110xxxxx 10xxxxxx0000 0800-0000 FFFF1110xxxx 10xxxxxx 10xxxxxx0001 0000-0010 FFFF11110xxx 10xxxxxx 10xxxxxx 10xxxxxx测试测试之前我们看一下a = 'Αpple' ，b = 'Apple'，其中a和b是一样的吧。下面我们实际看一下子！>>> a = 'Αpple' >>> b = 'Apple' >>> print(a==b) False >>> bytes(a,'UTF-8').hex() 'ce9170706c65' >>> bytes(b,'UTF-8').hex() #后面的数字基本一样，那问题基本是出在第一个A上。 '4170706c65' >>> hex(ord('Α')) '0x391' >>> hex(ord('A')) #将单个字符转成10进制再转成16进制 '0x41'通过把两个字符串转成UTF8的编码再转成16进制看下，可以发现两个确实不是同一个A。所以当我们输入密码时候肯定是不对的。那为什么会造成这个问题呢？原因可能有些人电脑用的编码不一致，有些个别字符会导致差异。同样使用电脑过程中不要随意修改电脑系统的编码，尽量使用通用的utf8编码。当然除了这个情况，也有可能O和0的分不清1和小写的L 以及大写的i等，在手动输入密码时候也是经常会错。最后在声明一下，光在unicode编码中就存在很多易混淆的字符。BNF语法介绍在学习任何东西时候应当学会看官方文档，而在Python语法参考手册中1.2章节讲述的是后续章节用到的标注，用来对语法的解释。其实这里的标注是变种版的BNF语法标注，和标准版的稍微还有点区别，BNF语法其实有点像正则的用法。如下是BNF语法样例：if_stmt ::= "if" assignment_expression ":" suite ("elif" assignment_expression ":" suite)* ["else" ":" suite]::\= 表示“左侧是语法名称，右侧为语法规则的定义”例：exponent ::= "a"..."z" 意思为语法exponent是由单个小写a到z字母组成.| 表示“或者的意思”例：exponent ::= "a"|"z" 意思为语法exponent只能是a或者z.* 表示“对前一项重复0次或更多次”例：exponent ::= （"a"..."z"）* 意思为语法exponent是由0个（为空）或多个小写a到z字母组成.+ 表示“对前一项重复1次或更多次”例：exponent ::= （"a"..."z"）+ 意思为语法exponent是由1个（不为空）或多个小写a到z字母组成.[...] 表示“此项可选”例：exponent ::= "test"["a"|"z"] 意思为语法exponent可以是testa也可以是testz也可以是test.(...) 表示“分组”例：exponent ::= "test"（"a"|"z"） 意思为语法exponent可以是testa也可以是testz，但不能是test.<...> 表示“括起来的内容是对于所定义符号的非正式描述”例：exponent ::= 意思为语法exponent要满足这里的描述."..." 表示“引号中的字符序列本身，并非指代其它字”例：exponent ::= test"()" 意思为语法exponent先使用test语法，后面需要接上括号。如果test语法定义为由单个小写a到z字母组成,则当前exponent语法可以为 a()、b()...z()
职场中，不管是运维还是开发还是项目经理或者财务，都可能存在茫茫多的excel去做一些统计处理。恐怖的是，可能统计方式随着需求变化也会经常修改修正。作为职员，接到统计需求可能很模糊，一周、当周，反正通常没有具体时间节点，或者因其他原因。随之变化会导致不断修改我们的统计方式。另外我自己的记性并不是很好，面对简单的统计还能记得住，当面对复杂点的统计，可能到下次就忘记之前怎么统计的了。在多方面的原因下，迫使我有动力去开发了一款尽可能的适应不同数据表格、不同统计需求的Python小程序。小程序介绍代码逻辑整套逻辑可以循环运行，意味着可以创建多份规则配置表的Sheet，来批量统计！相当省事！这是我的代码链接。目录介绍src : 源代码目录data_interaction : OS、GUI等逻辑代码目录excel_process : 处理Excel相关逻辑代码目录match_process : 对应统计规则配置表逻辑代码目录input : 统计规则配置表存放位置output : 统计后输出表存放位置使用说明1、环境准备提前安装Git工具，最新版即可提前安装Python环境，最好是V3.10以上版本2、下载小程序git clone https://github.com/wuxingyu090/excel_statistics.git # 也可直接访问网站下载V1.0.1 Releas压缩包 cd excel_statistics/ # 进入小程序目录下 pip install -r requirements.txt # 安装依赖包3、准备需要统计的Excel数据表格将数据文件放置excel_statistics目录下4、规则配置在input目录下的config-keycount.xlsx 即为规则配置表。其中每个Sheet(除说明Sheet)，都是单独一套规则配置策略。Sheet名称可自定义，但需要与main.py 文件中rule_table_list列表数据同时修改成自定义名称，才能执行程序。（因为考虑到可能有存在未配置完成的规则配置策略无需运行的情况）后续这个可能会做成Gui进行选择。如下配置样例：count_nametablesheetl1_fieldl1_matchl2_fieldl2_matchl3_fieldl3_matchl4_fieldl4_matchl5_fieldl5_matchTEST1故障告警模板信息2172621.xlsx告警单列表故障发生时间2021-04-26 TEST2故障告警模板信息2172621.xlsx告警单列表故障发生时间2021-04-26故障级别Z2故障主题(必填)广州百科云A_X86_天河MIX故障主题(必填)半双工模式 ASO3故障告警模板信息2172621.xlsx告警单列表故障发生时间%H,22-08故障级别=,Z2故障主题(必填)广州百科云A_X86_天河MIX故障主题(必填)['ecs-yaochi.EcsGrandcanalDashboard服务提供异常','实例切换'] ADB4故障告警模板信息2172621.xlsx告警单列表故障发生时间%H,22-08故障级别=,Z1故障主题(必填)广州百科云A_X86_天河MIX故障主题(必填)Cgroup挂载检查 Count_name列：统计名称可自定义，相同规则配置sheet中，每个Count_name名称不可相同。Table列：填入放置excel_statistics目录下数据表格，如有多份数据表格，则填写对应不同的表名。可从多份数据表中进行统计。sheet列：填入对应表格中需要统计数据的Sheet名称l?_field：第几层级的字段名，填入需要统计数据的Sheet中的字段l?_match：第几层级的匹配，填入匹配规则。列表和字符串默认使用模糊匹配，带有'>,','<,','<>,','=,','%D,','%H,','%MI,'此类前缀表达式会进入对应规则匹配。如上l?_field 和 l?_match 样例规则配置表格中只到了l10 。（我感觉应该够了，不够可往后顺延）# main.py 文件 def run(show): rule_table_list = ['count_rule', 'count_rule1', 'count_rule2'] #上面右侧列表中count_runle* 替换成对应规则配置表中自定义sheet名称 config_keycount = 'config-keycount' for rule_table in rule_table_list: chart_product(config_keycount, rule_table) if show: show_chart(rule_table) else: pass5、运行程序python main.py --help # 查看帮助文档 Usage: main.py [OPTIONS] Options: --show BOOLEAN show 参数，默认False，不直接输出图表 --help Show this message and exit. python main.py # 统计数据以及图表输出至输出表 python main.py --show True # 统计数据以及图表输出至输出表，同时直接界面展示图表6、查看统计结果 后续实现当前V1版本Excel统计小程序还有很多不足，比如没有Gui，配置不是很方便，自定义匹配规则还比较少，还待后续逐步叠加更新。但因个人时间并不多。锦上添花的新功能可能跟新会比较缓慢！不过如果有使用上的Bug，还请提交Issue，小编博主会在一周内答复！同时也希望有人帮忙一起迭代新的统计规则功能！