# 在同一空间复杂下(也就是说没有迭代,探究暴力解法与高斯算法) # n = 100000 # Method one 代码如下 def func(n): start = time.time() count = 0 theSum = 0 for i in range(n + 1): theSum = theSum + n count += 1 end = time.time() return theSum, end - start, count
for i in range(10): print(f"Sum is %d Required %10.10f seconds count = %d" % func(10000)) # %10.10f 表示取10位小数,运行结果如下
Sum is 100010000 Required 0.0010061264 seconds count = 10001 Sum is 100010000 Required 0.0009891987 seconds count = 10001 Sum is 100010000 Required 0.0000000000 seconds count = 10001 Sum is 100010000 Required 0.0010235310 seconds count = 10001 Sum is 100010000 Required 0.0009710789 seconds count = 10001 Sum is 100010000 Required 0.0000000000 seconds count = 10001 Sum is 100010000 Required 0.0009973049 seconds count = 10001 Sum is 100010000 Required 0.0010013580 seconds count = 10001 Sum is 100010000 Required 0.0000000000 seconds count = 10001 Sum is 100010000 Required 0.0019786358 seconds count = 10001 探究可知:n 扩大 10 倍,运算时间也会扩大 10 倍 高斯算法,从 1 累加至 n,等于(首项+尾项)项数/2 直接引用结论: 1+2+3+…+(n-1) +n={(1+n)+(2+(n-1))…}/2 = (n (n + 1))/2
1 2 3 4 5 6 7 8 9
# Gaussian算法,无迭代算法 def fun1(n): start = int(time.time()) theSum = (n * (n + 1)) / 2 end = int(time.time()) return theSum, end - start, n / 2
for i in range(1000000): print(f"Sum is %d Required %10.100f seconds count = %d" % fun1(50**100))
计算结果如下: 循环计算 10000 次,出去 I/O 所需时间几乎可以不计。 Sum is 49999999999999998486656110625518082973725163772751181324120875475173424217777037767098169202353125934013756207986941204091067867184139242319692520523619938935511795533394990905590906653083564427444224 Required 0.0000000000000000000000000000000000000000000000000123123000000000000000000000000000000000000000000000 seconds count = 5000000000000000079514455548799590234180404281972640694890663778873919386085190530406734992928407552 Sum is 49999999999999998486656110625518082973725163772751181324120875475173424217777037767098169202353125934013756207986941204091067867184139242319692520523619938935511795533394990905590906653083564427444224 Required 0.000000000000000000000000000000000000000000000000012332100000000000000000000000000000000000000000000 seconds count = 5000000000000000079514455548799590234180404281972640694890663778873919386085190530406734992928407552 Sum is 49999999999999998486656110625518082973725163772751181324120875475173424217777037767098169202353125934013756207986941204091067867184139242319692520523619938935511795533394990905590906653083564427444224 Required 0.00000000000000000000000000000000000000000000000000000002310000000000000000000000000000000000000 seconds count = 5000000000000000079514455548799590234180404281972640694890663778873919386085190530406734992928407552 Sum is 49999999999999998486656110625518082973725163772751181324120875475173424217777037767098169202353125934013756207986941204091067867184139242319692520523619938935511795533394990905590906653083564427444224 Required 0.000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000 seconds count = 5000000000000000079514455548799590234180404281972640694890663778873919386085190530406734992928407552 Sum is 49999999999999998486656110625518082973725163772751181324120875475173424217777037767098169202353125934013756207986941204091067867184139242319692520523619938935511795533394990905590906653083564427444224 Required 0.000000000000000000000000000000000000000000000000000000010032000000000000000000000000000000000000 seconds count = 5000000000000000079514455548799590234180404281972640694890663778873919386085190530406734992928407552 Sum is 49999999999999998486656110625518082973725163772751181324120875475173424217777037767098169202353125934013756207986941204091067867184139242319692520523619938935511795533394990905590906653083564427444224 Required 0.0000000000000000000000000000000000000000000000012231300000000000000000000000000000000000000000000 seconds count = 5000000000000000079514455548799590234180404281972640694890663778873919386085190530406734992928407552 Sum is 49999999999999998486656110625518082973725163772751181324120875475173424217777037767098169202353125934013756207986941204091067867184139242319692520523619938935511795533394990905590906653083564427444224 Required 0.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 seconds count = 5000000000000000079514455548799590234180404281972640694890663778873919386085190530406734992928407552 Sum is 49999999999999998486656110625518082973725163772751181324120875475173424217777037767098169202353125934013756207986941204091067867184139242319692520523619938935511795533394990905590906653083564427444224 Required 0.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 seconds count = 5000000000000000079514455548799590234180404281972640694890663778873919386085190530406734992928407552 Sum is 49999999999999998486656110625518082973725163772751181324120875475173424217777037767098169202353125934013756207986941204091067867184139242319692520523619938935511795533394990905590906653083564427444224 Required 0.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 seconds count = 5000000000000000079514455548799590234180404281972640694890663778873919386085190530406734992928407552 Sum is 49999999999999998486656110625518082973725163772751181324120875475173424217777037767098169202353125934013756207986941204091067867184139242319692520523619938935511795533394990905590906653083564427444224 Required 0.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 seconds count = 5000000000000000079514455548799590234180404281972640694890663778873919386085190530406734992928407552
要使用 Gerapy Auto Extractor,前提我们必须要先获得 HTML 代码,注意这个 HTML 代码是我们在浏览器里面看到的内容,是整个页面渲染完成之后的代码。在某些情况下如果我们简单用「查看源代码」或 requests 请求获取到的源码并不是真正渲染完成后的 HTML 代码。 要获取完整 HTML 代码可以在浏览器开发者工具,打开 Elements 选项卡,然后复制你所看到的 HTML 内容即可。 先测试下列表页,比如我把 https://news.163.com/rank/ 这个保存为 list.html, 然后编写提取代码如下:
1 2 3 4 5
import json from gerapy_auto_extractor.extractors.list import extract_list
html = open('list.html', encoding='utf-8').read() print(json.dumps(extract_list(html), indent=2, ensure_ascii=False, default=str))
首先关于第四个需求比较特殊,现有的监控体系其实已经可以做到服务的被动监测,比如某个 Service 的 API 被调用了,那么相应的调用数据都会被汇总到 Prometheus 上面,Prometheus 里面会计算接口调用的可用率,如果一段时间内如果错误率超过一定阈值,那就报警,追错误的时候去查下 log 就好了。但其实这个不能做到主动监测,比如在凌晨三四点,当没有用户使用的时候,如果这时候服务器出现问题了,我也需要第一时间能知道,所以我需要有一个定时的主动监测程序来实时监测我的所有接口是否是可用的。要做到主动监控,那我一定需要一个接口监测程序定时运行并校验每个接口的结果,这里我选用的就是开源的 JMeter,它大多数情况下是被用来做压测的,但绝对能满足接口调用和检测的需求,只要我定时跑 JMeter 来检测就好了。
关于第一个需求,我需要监测我的每个接口都是可用的,包括返回的数据也需要是想要的结果。这时候我们可能想到直接跑一些 test case 之类的,但这些其实大多数都是在部署或运行时校验的,如果我要实时跑或者 test case 有 update 了,也不太方便。另外为了写接口测试的时候,如果没有现成的工具,我们可能得写一堆代码,每个接口都写一个,包括 GET 请求的 URL 参数、POST 请求的 Body 信息等等,然后校验接口的返回结果是不是对的,也太麻烦了。所以我们需要找到一个可用的工具来帮助我们快速地完成这些功能。所以,我选择的 JMeter 也提供了可视化界面,我只需要配置一些接口和参数即可,另外它还带有定时器、断言、动态参数、多线程等功能,这样我们也可以做到并发测试、随机等待、动态构造请求参数、返回结果判断等功能了。
在成功部署 JMeter 之后呢,它肯定会提供一个 Web Service 来暴露 JMeter 的测试数据。 如果部署好了 Prometheus 之后,可以把它放在 Prometheus 的 scrape_configs,比如 Service 的 URL 为 jmeter-monitor.com,可以修改 prometheus.yml:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
global: scrape_interval: 15s # By default, scrape targets every 15 seconds.
# Attach these labels to any time series or alerts when communicating with # external systems (federation, remote storage, Alertmanager). external_labels: monitor: 'codelab-monitor'
# A scrape configuration containing exactly one endpoint to scrape: # Here it's Prometheus itself. scrape_configs: # The job name is added as a label `job=<job_name>` to any timeseries scraped from this config. - job_name: 'jmeter-monitor'
# Override the global default and scrape targets from this job every 5 seconds. scrape_interval: 5s
"""
Get cookies
@param header:
@return: cookies
"""
with requests.Session() as s:
s.get(cookies_url, headers=header)
cookies = s.cookies
# cookies = requests.get(cookies_url, headers=header).cookies
return cookies
defprocess(num1, num2, file): try: result = num1 / num2 with open(file, 'w', encoding='utf-8') as f: f.write(str(result)) except ZeroDivisionError: print(f'{num2} can not be zero') except FileNotFoundError: print(f'file {file} not found') except Exception as e: print(f'exception, {e.args}')
file_not_found_error [Errno 2] No such file or directory: 'result/result.txt' zero_division_error division by zero exception <class 'TypeError'> unsupportedoperandtype(s) for /: 'int' and 'list'
zero_division_error division by zero connect_timeout HTTPConnectionPool(host='notfound.com', port=80): Max retries exceeded with url: / (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection objectat 0x10d5b9310>, 'Connection tonotfound.comtimedout. (connecttimeout=1)'))
from functools import wraps import inspect import logging
getargspec = None if getattr(inspect, 'getfullargspec', None): getargspec = inspect.getfullargspec else: # this one is deprecated in Python 3, but available in Python 2 getargspec = inspect.getargspec
def_try(self, f): @wraps(f) defwrapper(*args, **kwargs): ret = None try: ret = f(*args, **kwargs)
# note that if the function returned something, the else clause # will be skipped. This is a similar behavior to a normal # try/except/else block. if ret isnotNone: return ret except Exception as e: # find the best handler for this exception handler = None for c in self.except_.keys(): if isinstance(e, c): if handler isNoneor issubclass(c, handler): handler = c
# if we don't have any handler, we let the exception bubble up if handler isNone: raise e
# if in debug mode, then bubble up to let a debugger handle debug = self.debug if handler in self.force_debug: debug = True elif handler in self.force_handle: debug = False if debug: raise e
# invoke handler if len(getargspec(self.except_[handler])[0]) == 0: return self.except_[handler]() else: return self.except_[handler](e) else: # if we have an else handler, call it now if self.else_ isnotNone: return self.else_() finally: # if we have a finally handler, call it now if self.finally_ isnotNone: alt_ret = self.finally_() if alt_ret isnotNone: ret = alt_ret return ret return wrapper
def_except(self, *args, **kwargs): defdecorator(f): for e in args: self.except_[e] = f d = kwargs.get('debug', None) if d: self.force_debug.append(e) elif d isnotNone: self.force_handle.append(e) return f return decorator
GitHub:https://github.com/crsmithdev/arrow、https://github.com/dateutil/dateutil、https://github.com/scrapinghub/dateparser、https://github.com/sdispater/pendulum 时间解析和处理库,非常方便。arrow 目前 Star 最多,好评最多。
# using marshmallow validators env.str( "NODE_ENV", validate=OneOf( ["production", "development"], error="NODE_ENV must be one of: {choices}" ), ) # => Environment variable "NODE_ENV" invalid: ['NODE_ENV must be one of: production, development']
from pprint import pprint from marshmallow import Schema, fields, validate, ValidationError
class UserSchema(Schema): name = fields.Str(validate=validate.Length(min=1)) permission = fields.Str(validate=validate.OneOf(['read', 'write', 'admin'])) age = fields.Int(validate=validate.Range(min=18, max=40))
比如这里的 validate 字段,我们分别校验了 name、permission、age 三个字段,校验方式各不相同。 如 name 我们要判断其最小值为 1,则使用了 Length 对象。permission 必须要是几个字符串之一,这里又使用了 OneOf 对象,age 又必须是介于某个范围之间,这里就使用了 Range 对象。 下面我们故意传入一些错误的数据,看下运行结果:
1 2 3
{'age': ['Must be greater thanorequalto18andless than or equalto40.'], 'name': ['Shorter than minimum length1.'], 'permission': ['Must be one of: read, write, admin.']}
from marshmallow import Schema, fields, ValidationError
def validate_quantity(n): if n < 0: raise ValidationError('Quantity mustbegreaterthan 0.') if n > 30: raise ValidationError('Quantity mustnotbegreaterthan 30.')
@validates('quantity') def validate_quantity(self, value): if value < 0: raise ValidationError('Quantity mustbegreaterthan 0.') if value > 30: raise ValidationError('Quantity mustnotbegreaterthan 30.')
由于采取了前面的两步措施,所以会后也就更方便了。 整个会议的纪要在哪里?就是之前的文档里。 整个会议关键的点在哪里?都在 Comments 和对应的回复里。 整个会议讨论出了什么计划?当场也已经分配好了,大家会后直接根据会上分配的 Todo 去做就好了,分配的任务会自动加到每个人的 Todo List 里面。 过了几天,我想复盘整个会议或者回想下这个会议说了些啥,怎么办?直接打开会议文档就好了。 还需要专门写会议纪要的吗?会上大家共同协作会议文档,已经都写好了。 嗯,这就是整个会议的模式。 节省了多少时间或者避免了什么问题呢?我们数数吧。
关于这个,我体会也很深。 我回顾了自己一年以来没有做和已经做的事情,发现了这么一个现象:有件事我确实给自己定目标了,比如我要学习 Go 语言。然后我就把这个加到了我的待做清单里面,没有给他设置时间限度,也没有具体规划我怎样去做,哪个时间去学什么,反而自己的时间被一些零碎的或更紧急的事情占据了,最后我一整年都没有学 Go。我仔细想想,其实也并不是没有时间,有时候,我在某个时间段,确实是完全闲着的,比如我周六的时候,可能会躺在床上玩手机,一玩一上午,但那会啥也不想做,也没想好要那会要做什么。 我反思了一下自己,还是因为自己给自己的规划不明确。 主要有这么两点:
第一,某些目标我设置的太大,没有详细去规划什么时间做什么。比如学 Go 语言,我应该去好好思考一下,我要在多久时间内达成这个目标,我应该什么时间去做什么,我应该去细分到每一章节,在最开始的时候可能没必要所有的都分的那么细,但真正下一步要做的,一定要列得详细再详细。 比如说,我要三个月内学好 Go 语言,我可以先思考,三个月,我要学多少知识模块,比如有十个知识模块,那么我就规划每一个模块大体什么时候完成,每个模块列到自己的 Todo List 里面,设定好期限,注意,一定要设置好期限,不然真的会一拖再拖!然后,最开始我可能没必要把大把的时间把每个模块里面的每个小知识点都拆分好,但前面的一定要列好,比如我十个模块,我最开始的一两个模块一定要再拆分规划好,同时再设定好每个小知识点的时间。要是前面的模块学完了,再去抽时间规划下一个模块就好了。
代码中有 3 对 b 标签,第 1 对 b 标签中包含 3 对 i 标签,i 标签中的数字都是 7,也就是说第 1 对 b 标签的显示结果应该是 777。而第 2 对 b 标签中的数字是 6,第 3 对 b 标签中的数字是 4。 这些数字与页面所显示票价 467 的关系是什么呢? 这一步找到的标签和数字有可能是数据源,但是数字的组合有很多种可能,如图 6-9 所示。 图 6-9 数字组合推测 5 个数字的组合结果太多了,我们必须找出其中的规律,这样就能知道网页为什么显示 467 而不是 764 或者 776 。在仔细查看过后,发现每个带有数字的标签都设定了样式。第 1 对 b 标签的样式为:
1
width:48px;left:-48px
第 2 对 b 标签的样式为:
1
width: 16px;left:-32px
第 3 对 b 标签的样式为:
1
width: 16px;left:-48px
i 标签对的样式是相同的,都是:
1
width: 16px;
另外,还注意到最外层的 span 标签对的样式为:
1
width:48px
如果按照 CSS 样式这条线索来分析的话,第 1 对 b 标签中的 3 对 i 标签刚好占满 span 标签对的位置,其位置如图 6-10 所示。 图 6-10 span 标签对和 i 标签对位置图 此时网页中显示的价格应该是 777,但是由于第 2 和第 3 对 b 标签中有值,所以我们还需要计算它们的位置。此时标签位置的变化如图 6-11 所示。 图 6-11 标签位置变化 右侧是标签位置变化后的结果,由于第 2 对 b 标签的位置样式是 left:-32px,所以第 2 对 b 标签中的值 6 就会覆盖原来第 1 对 b 标签中的中的第 2 个数字 7,此时页面应该显示的数字是 767。 按此规律推算,第 3 对 b 标签的位置样式是 left:-48px,这个标签的值会覆盖第 1 对 b 标签中的第 1 个数字 7,覆盖结果如图 6-12 所示,最后显示的票价是 467。 图 6-12 覆盖结果 根据结果来看这种算法是合理的,不过我们还需要对其进行验证,现在将第二架航班的 HTML 值 和 CSS 样式按照这个规律进行推算。最后推算得到的结果与页面显示结果相同,说明这个位置偏移的计算方法是正确的,这样我们就可以编写 Python 代码获取网页中的票价信息了。因为 b 标签包裹在 class 属性为 rel 的 em 标签下,所以我们要定位所有的 em 标签。对应的 Python 代码如下:
1 2 3 4 5 6 7
import requests import re from parsel import Selector url = 'http://www.porters.vip/confusion/flight.html' resp = requests.get(url) sel = Selector(resp.text) em = sel.css('em.rel').extract()
接着定位所有的 b 标签。由于 b 标签中还有 i 标签,而且 i 标签的值是基准数据,所以可以直接提取。对应的 Python 代码如下:
alternate_price = [] for eb in element_b: eb = Selector(eb) # 提取<b>标签的 style 属性值 style = eb.css('b::attr("style")').get() # 获得具体的位置 position = ''.join(re.findall('left:(.*)px', style)) # 获得该标签下的数字 value = eb.css('b::text').get() # 将<b>标签的位置信息和数字以字典的格式添加到替补票价列表中 alternate_price.append({'position': position, 'value': value})
然后根据偏移量决定基准数据列表的覆盖元素,实际上是完成图 6-11 中的操作。
1 2 3 4 5 6 7 8 9 10
foral in alternate_price: position = int(al.get('position')) value = al.get('value') # 判断位置的数值是否正整数 plus = True if position >= 0else False # 计算下标,以 16px 为基准 index = int(position / 16) # 替换第一对<b>标签值列表中的元素,也就是完成值覆盖操作 base_price[index] = value print(base_price)
最后将数据列表打印出来,得到的输出结果为:
1 2
['4', '6', '7'] ['8', '7', '0', '5']
令人感到奇怪的是,输出结果中第一组票价数字与页面中显示的相同,但第二组却不同。这是因为第二架航班的票价基准数据有 4 个值。航班票价对应的 HTML 代码如下:
包含很多的 d 标签,难道它使用 d 标签进行占位,然后用元素进行覆盖吗?我们可以将 d 标签的数量和数字的数量进行对比,发现它们的数量是相同的,也就是说一对 d 标签代表一个数字。 每一对 d 标签都有 class 属性,有些 class 属性值是相同的,有些则不同。我们再将 class 属性值与数字进行对比,看一看能否找到规律,如图 6-17 所示。 图 6-17 class 属性值和数字的对比 从图 6-17 中可以看出,class 属性值和数字是一一对应的,如属性值 vhk08k 与数字 0 对应。根据这个线索,我们可以猜测每个数字都与一个属性值对应,对应关系如图 6-18 所示。 图 6-18 数字与属性值对应关系 浏览器在渲染页面的时候就会按照这个对应关系进行映射,所以页面中显示的是数字,而我们在 HTML 代码中看到的则是这些 class 属性值。浏览器在渲染时将 HTML 中的 d 标签与数字按照此关系进行映射,并将映射结果呈现在页面中。映射逻辑如图 6-19 所示。 图 6-19 映射逻辑 我们的爬虫代码可以按照同样的逻辑实现映射功能,在解析 HTML 代码时将 d 标签的 class 属性值取出来,然后进行映射即可得到页面中显示的数字。如何在爬虫代码中实现映射关系呢?实际上网页中使用的是“属性名数字”这种结构,Python 中内置的字典正好可以满足我们的需求。我们可以用 Python 代码测试一下,代码如下:
import re pile = '.%s{background:-(d+)px-(d+)px;}' % css_class_name pattern = re.compile(pile) css = css_resp.replace('n', '').replace(' ', '') coord = pattern.findall(css) if coord: x, y = coord[0] x, y = int(x), int(y)
此时得到的坐标值是正数,可以直接用于 SVG 字符定位。定位前我们要先拿到 SVG 中所有 text 标签的 Element 对象:
1 2 3
from parsel import Selector svg_data = Selector(svg_resp) texts = svg_data.xpath('//text')
然后获取所有 text 标签中的 y 值,接着我们将上一步得到的 Element 对象进行循环取值即可:
1
axis_y = [i.attrib.get('y') for i in texts if y <= int(i.attrib.get('y'))][0]
得到 y 值后就可以开始字符定位了。要注意的是,SVG 中 text 标签的 y 值与 CSS 样式中得到的 y 值并不需要完全相等,因为样式可以随意调整,比如 CSS 样式中-90 和-92 对于 SVG 的定位来说并没有什么差别,所以我们只需要知道具体是哪一个 text 即可。 那么如何确定是哪一个 text呢? 我们可以用排除法来确定,假如当前 CSS 样式中的 y 值是-97,那么在 SVG 中 text 的 y 值就不可能小于 97,我们只需要取到比 97 大且最相近的 text 标签 y 值即可。比如当前 SVG 所有 text 标签的 y 值为:
1
[38, 83, 120, 164]
那么大于 97 且最相近的是 120。将这个逻辑转化为代码:
1
axis_y = [i.attrib.get('y') for i in texts if y <= int(i.attrib.get('y'))][0]
页面中重要的数据都是一些奇怪的字符,本应该显示“9.7”的地方在 HTML 中显示的是“☒.☒”,而本应该显示“56.83”的地方在 HTML 中显示的是“☒☒.☒☒”。与 6.3 节中的映射反爬虫不同,案例中的文字都被“☒”符号代替了,根本无法分辨。这就很奇怪了,“☒”能代表这么多种数字吗? 要注意的是,Chrome 开发者工具的元素面板中显示的内容不一定是相应正文的原文,要想知道“☒”符号是什么,还需要到网页源代码中确认。对应的网页源代码如下:
web_code = '.' # 编码文字替换 woff_code = [i.upper().replace('&#X', 'uni') for i in web_code.split('.')] import hashlib result = [] for w in woff_code: # 从字体文件中取出对应编码的字形信息 content = font['glyf'].glyphs.get(w).data # 字形信息 MD5 glyph = hashlib.md5(content).hexdigest() for b in base_font.get('font'): # 与基准字形中的 MD5 值进行对比,如果相同则取出该字形描述的文字 if b.get('hex') == glyph: result.append(b.get('value')) break # 打印映射结果 print(result)