Month: 8月 2020

Python 3 中的 dataclass

在 Python 中,如果要为一个类添加一些数据成员的话,需要做的事情还挺多,比如说编写 __init__,
__str__ 这些函数,代码都是重复的,没啥意义。在 Python 3.7 中,终于添加了一个语法糖,叫做
dataclass. 下面我们就来看一下吧~

# 注意!包名叫 dataclasses, 多了个 es
from dataclasses import dataclass

@dataclass
class InventoryItem:
    """Class for keeping track of an item in inventory."""
    name: str
    unit_price: float
    quantity_on_hand: int = 0

    def total_cost(self) -> float:
        return self.unit_price * self.quantity_on_hand

上面的代码就相当于以前的:

def __init__(self, name: str, unit_price: float, quantity_on_hand: int=0):
    self.name = name
    self.unit_price = unit_price
    self.quantity_on_hand = quantity_on_hand

def __repr__(self):
    ...

def __eq__(self):
    ...

...

我们知道,在 Python 的默认参数中,使用 mutable(可变) 的对象是一种常见的坑,在 dataclass 中当然也存在
了,还好标准库中给我们提供了一个方法。

# 会报错
@dataclass
class Request:
    headers: dict = {}

# 相当于
class Request:
    def __init__(self, headers={}):
        self.headers = headers

# 正确的写法
from dataclasses import field

@dataclass
class Request:
    headers: dict = field(default_factory=dict)

字典这种类型是比较容易想起来不能直接做参数的,比较坑的是对于其他的自定义对象,Python 解释器并不会提
示有问题,比如说这样:

# 千万别这么做
@dataclass
class Request:
    headers: Headers = Headers()

这时候坑爹的事情就发生了,每次创建新的 Request 对象引用的都是同一个 Headers 对象,也就是在声明这个类
的同时产生的这个 Headers 对象!原因也很简单,就像是上面的 dict 一样,这个 Headers 并不是在 init 函数
中,所以只会产生一次。所以,需要牢记的是:dataclass 中的所有对象默认值就相当于函数的默认参数,永远不
要传递一个 mutable 就好了。

# 上面的例子相当于
class Request:
    def __init__(self, headers=Headers()):
        self.headers = headers

# 正确的做法
@dataclass
class Request:
    headers: Headers = field(default_factory=Headers)

dataclasses 模块中还提供了 asdict 方法,这样就可以方便地转换为 json 对象啦。

from dataclasses import asdict

@dataclass
class Request:
    url: str = ""
    method: str = ""

req = asdict(Request())

参考资料

  1. https://docs.python.org/3/library/dataclasses.html

海外爬虫 IP 池

https://github.com/constverum/ProxyBroker/blob/master/proxybroker/providers.py

https://list.proxylistplus.com/SSL-List-1

https://list.proxylistplus.com/Fresh-HTTP-Proxy-List-1

https://cool-proxy.net/
https://github.com/imWildCat/scylla/blob/master/scylla/providers/coolproxyprovider.py

https://free-proxy-list.net/
https://github.com/imWildCat/scylla/blob/master/scylla/providers/freeproxylist_provider.py

https://proxyhttp.net/
https://github.com/imWildCat/scylla/blob/master/scylla/providers/httpproxyprovider.py

https://www.ipaddress.com/proxy-list/
https://github.com/imWildCat/scylla/blob/master/scylla/providers/ipaddress_provider.py

http://proxy-list.org/english/index.php
https://github.com/imWildCat/scylla/blob/master/scylla/providers/proxylistprovider.py

https://raw.githubusercontent.com/sunny9577/proxy-scraper/master/proxies.json
https://github.com/imWildCat/scylla/blob/master/scylla/providers/proxyscraperprovider.py

http://www.proxylists.net/countries.html
https://github.com/imWildCat/scylla/blob/master/scylla/providers/proxylists_provider.py

https://github.com/imWildCat/scylla/blob/master/scylla/providers/proxynova_provider.py

http://pubproxy.com/api/proxy?limit=5&format=txt&type=http&level=anonymous&lastcheck=60&nocountry=CN

https://github.com/imWildCat/scylla/blob/master/scylla/providers/rmccurdy_provider.py

https://github.com/imWildCat/scylla/blob/master/scylla/providers/spysmeprovider.py

https://github.com/imWildCat/scylla/blob/master/scylla/providers/spysoneprovider.py

https://github.com/imWildCat/scylla/blob/master/scylla/providers/thespeedXprovider.py

https://proxy-daily.com/

http://ab57.ru/downloads/proxyold.txt

http://www.proxylists.net/http.txt

http://www.proxylists.net/http_highanon.txt

http://pubproxy.com/api/proxy?limit=5&format=txt&type=http&level=anonymous&lastcheck=60&nocountry=CN
http://pubproxy.com/api/proxy?limit=5&format=txt&type=http&level=anonymous&last_check=60&country=CN

http://free-proxy.cz/zh/proxylist/country/CN/all/ping/all
https://github.com/phpgao/proxypool/blob/master/job/htmlcz.go

http://nntime.com/proxy-updated-01.htm
https://github.com/phpgao/proxypool/blob/master/job/htmlnntime.go

https://premproxy.com/list/time-01.htm
https://github.com/phpgao/proxypool/blob/master/job/htmlpremproxy.go

https://github.com/phpgao/proxypool/blob/master/job/htmlproxydb.go

https://github.com/phpgao/proxypool/blob/master/job/htmlsite_digger.go

https://github.com/phpgao/proxypool/blob/master/job/htmlultraproxies.go

https://github.com/phpgao/proxypool/blob/master/job/htmlus_proxy.go

https://github.com/phpgao/proxypool/blob/master/job/jsoncool_proxy.go

https://github.com/phpgao/proxypool/blob/master/job/realiveproxy.go

https://github.com/phpgao/proxypool/blob/master/job/reblackhat.go

https://github.com/phpgao/proxypool/blob/master/job/redogdev.go

https://github.com/phpgao/proxypool/blob/master/job/refreeip.go

https://github.com/phpgao/proxypool/blob/master/job/rehttptunnel.go

https://github.com/phpgao/proxypool/blob/master/job/remy_proxy.go

https://github.com/phpgao/proxypool/blob/master/job/renewproxy.go

https://github.com/phpgao/proxypool/blob/master/job/reproxyiplist.go

https://github.com/phpgao/proxypool/blob/master/job/reproxylist.go

https://github.com/phpgao/proxypool/blob/master/job/rexseo.go

https://github.com/derekhe/ProxyPool/blob/master/lib/proxybroker/providers.py

https://github.com/Jiramew/spoon/blob/master/spoonserver/proxy/listendeprovider.py

https://github.com/Jiramew/spoon/blob/master/spoonserver/proxy/nordprovider.py

https://github.com/Jiramew/spoon/blob/master/spoonserver/proxy/pdbprovider.py

https://github.com/Jiramew/spoon/blob/master/spoonserver/proxy/plpprovider.py

https://github.com/Jiramew/spoon/blob/master/spoonserver/proxy/premprovider.py

https://github.com/Jiramew/spoon/blob/master/spoonserver/proxy/sslprovider.py

https://github.com/Jiramew/spoon/blob/master/spoonserver/proxy/webprovider.py

https://www.freeproxy.world/

http://proxydb.net/

http://www.xsdaili.cn/

https://github.com/bluet/proxybroker2/blob/master/proxybroker/providers.py

https://github.com/nicksherron/proxi/blob/master/internal/providers.go

# def freeProxy10():
#     """
#     墙外网站 cn-proxy
#     :return:
#     """
#     urls = ['http://cn-proxy.com/', 'http://cn-proxy.com/archives/218']
#     request = WebRequest()
#     for url in urls:
#         r = request.get(url, timeout=10)
#         proxies = re.findall(r'<td>(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})</td>[\w\W]<td>(\d+)</td>', r.text)
#         for proxy in proxies:
#             yield ':'.join(proxy)

# @staticmethod
# def freeProxy11():
#     """
#     https://proxy-list.org/english/index.php
#     :return:
#     """
#     urls = ['https://proxy-list.org/english/index.php?p=%s' % n for n in range(1, 10)]
#     request = WebRequest()
#     import base64
#     for url in urls:
#         r = request.get(url, timeout=10)
#         proxies = re.findall(r"Proxy\('(.*?)'\)", r.text)
#         for proxy in proxies:
#             yield base64.b64decode(proxy).decode()

# @staticmethod
# def freeProxy12():
#     urls = ['https://list.proxylistplus.com/Fresh-HTTP-Proxy-List-1']
#     request = WebRequest()
#     for url in urls:
#         r = request.get(url, timeout=10)
#         proxies = re.findall(r'<td>(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})</td>[\s\S]*?<td>(\d+)</td>', r.text)
#         for proxy in proxies:
#             yield ':'.join(proxy)

Rails 学习笔记

因为工作的关系,需要接受一个 RoR 的项目,花一下午时间学习一下 Ruby 和 Rails. 还好在大学的时候读过关于 Ruby 的一本书,语法基本还是知道一些的。

Rails 是一个典型的 MVC 的 web 框架。

Controller 需要继承 ApplicationController 基类。对于每一个路径 http://xxx.com/my-controller/action 都对应了 app/controllers/my_controller.rb 下的 MyController 类的 action 方法,然后会渲染 app/views/my-contoller/action.html.erb 文件。即使方法是空的,也会渲染对应的 erb/haml 模板。

rails 会把 controller 中所有的类变量都传递到模板中,所以如果有需要渲染的变量,直接使用类变量就行了,而不用显式传递。

可以使用 before_action 来增加一些钩子函数,比如要求登录之类的。

目录结构

  • app/ 目录是主要的代码目录。
    • app/controllers/ 存放 controllers
    • app/views/ 存放 views, 也就是 erb 或者 haml 的模板代码
  • lib/tasks/*.rake 中存放的是可以通过 rake 调用的任务。
  • 路由表位于 config/routes.rb 文件
  • config/schedule.rb 文件存放 whenever 管理的 cron 任务。

路由

config/routes.rb 中的 DSL 可以使用不同的方法定义路由:

  • resources 定义一个典型的 REST 资源。

Ruby 语法基础

Ruby 的 Slice 和 Python 是不同的,有以下两点:

  • Ruby 使用 .. 而不是 :. string[0..8]
  • Ruby 的 slice 表示的是闭区间,而不是其他语言的前开后闭区间。

一般语言中会使用 is_valid 来表示一个布尔值,而 ruby 中习惯使用 valid?, 其中的 ? 就是一个普通的字符。

字符串的方法

len(s) -> s.length
s.replace() -> s.sub() or s.gsub()
f"hello {name}" -> hell #{name}

符号

我实在不知道符号这个东西有什么用处,string 本身不就应该是 internized 的么

ruby 算是比较有创新精神,可以使用 n.times 来表示一个循环。

5.times do
  puts "Hello, World!"
end

块还可以接收参数,使用 |

5.times do |i|
  puts "#{i}: Hello, World!"
end

在 Python 中如果你输入 import braces 那么会得到 not a chance 错误,但是在 ruby 中,我们是可以使用大括号的。😂

数组和字典

<< 可以用来 append
.sort 不会改变原数组。
还可以使用 .each + 块来遍历数组。

字典

使用字符串作为 key

prices = {"apples" => 3, "oranges" => 1, "carrots" => 12}

使用符号作为 key

{apples: 3, oranges: 1, carrots: 12}

ruby 中使用 if/elsif/else 语句,注意其中多了一个 s.

ruby 中使用 &&|| 来表示 andor.

nil 表示 None.

Ruby 中字符串是可变的

Ruby 中除了 false, nil 以外都是真的,也就是 0 "" 也是真的

面向对象

class 中使用 attr_accessor 来设置属性。方法签名中不需要使用 self 参数,函数体中也不需要使用 self 来访问属性。使用 attr_accessor 定义的属性可以认为是 public 的,而如果想要 private 的属性的话,可以使用 @var 语法,@variable 不需要声明,直接使用就行。

创建实例的话需要使用 .new 方法。MyClass.new. Ruby 中的构造函数是 initialize, 相比 __init__ 来说,太难拼写了。

class Student
  attr_accessor :first_name, :last_name, :primary_phone_number

  def introduction  # 这里没有 self
    puts "Hi, I'm #{first_name}!"  # 这里不用 self
  end
end

frank = Student.new
puts frank.first_name
frank.first_name = "Max"
frank.introduction  # 函数调用不用括号

Ruby 中的 require 使用来实现其他语言的 inlcude 或者 import 操作的,而 include 关键字是用来实现 mixin 的。

捕获异常

Ruby 中使用 begin/rescue/ensure 来表示 try/except/finally 的逻辑。有个语法糖,如果在函数中的话,可以直接以 def 未开始,而不用显式地 begin 了。

begin
  # ...
rescue
  # ...
ensure
  # this always runs
end

参考资料

  1. https://www.jianshu.com/p/99b4552b512f
  2. https://stackoverflow.com/questions/12924080/difference-between-instance-variable-and-attr-accessor
  3. https://www.ruby-lang.org/en/documentation/quickstart/
  4. https://guides.rubyonrails.org/getting_started.html
  5. https://docs.ruby-lang.org/en/2.4.0/syntax/exceptions_rdoc.html
  6. https://www.rubyguides.com/2019/02/ruby-rake/

抓取新浪微博的数据

新浪微博的数据总体来说可以通过几个接口获取:

  1. 网页版 (weibo.com)
  2. 移动版 (m.weibo.cn) JSON 接口数据很丰富
  3. WAP 版 (weibo.cn), 数据经常不全。和上面两个接口的 ID 不是一套。
  4. 开放平台的接口,需要创建一个应用然后使用,感觉局限性挺大的,除非抓取量很小。

新浪微博的数据有两套 id, 一个叫 id/mid, 是数字类型的,另一套叫做 bid, 是字符类型的。

根据关键词抓取指定微博

可以使用移动端的微博,翻页也不需要登录,随便撸

https://m.weibo.cn/api/container/getIndex?containerid=100103type%3D1%26q%3D%E8%8B%B9%E6%9E%9C%E8%BE%93%E5%85%A5%E6%B3%95&page_type=searchall&page=99

抓取微博的评论

这个接口翻页需要登录

https://m.weibo.cn/comments/hotflow?id=4282494510984677&mid=4282494510984677&max_id_type=0

抓取单条微博的接口

不需要登录,随便撸

https://m.weibo.cn/statuses/show?id=JgPmzBaKZ

抓取用户微博

不需要登录,翻页也不需要,随便撸

https://m.weibo.cn/api/container/getIndex?uid=5524254784&t=0&luicode=10000011&containerid=1076035524254784&since_id=4378269463566752

几个尚未查看的项目

  1. https://github.com/nghuyong/WeiboSpider

参考

  1. 移动端关键词抓取
  2. 微博搜索 API
  3. 移动端抓包

为什么不要使用 ORM?

连外键都不用的今天,再用 ORM 还有什么意义呢?别人云亦云,认真思考下这个问题。

  • 兼容不同数据库?一般一个项目几乎是不可能更换数据库的。而开发和线上使用不同的数据库是一种非常危险的行为。
  • 比使用存储过程好?我从来没用过存储过程。
  • 减少了很多复制属性的重复工作?对于静态语言,感觉自动生成的 DAL 更好,对于动态语言,直接一个字典搞定,根本不存在这个问题。
  • 自动转换类型?这个也不应该是 ORM 的一部分,而应该是 DB API 的一部分。

总之,ORM 就是计算机科学界的越南战争泥坑(美国视角), SQL 和面向对象本来就是不 match 的,非要强行 map 起来,能舒服了才怪。

ORM 的缺点则很致命:无法控制生成的 SQL.

ORM 隐藏了 SQL 语句,使得我们没法使用 SQL 的方式思考,容易写出性能较低的代码。ORM 生成的 SQL 有的也很低效。

在我们编写 SQL 相关的程序的时候很容易犯的一个错误是 “N+1” 查询,也就是说本来应该用一个语句实现的查询,我们却使用了 N+1 个查询。

读取的数据过多。一般情况下,我们可能只需要读取一两个字段,但是 ORM 默认的确是 select *, 导致性能下降。

如果使用 ORM 的话,实际上你在学习一门新的 DSL, 而且这个 DSL 还不是很通用。这点其实是最最重要的问题了,sql 的语法是 universal 的,是到处可用的,作为一个程序员你必须也不可能绕过去。

ORM 库往往和数据库的链接池,也就是 IO 部分耦合在一起,导致在需要不同的链接管理方式的时候非常难以修改。

参考

  1. https://stackoverflow.com/questions/494816/using-an-orm-or-plain-sql
  2. https://medium.com/@mithunsasidharan/should-i-or-should-i-not-use-orm-4c3742a639ce
  3. https://stackoverflow.com/questions/448684/why-should-you-use-an-orm
  4. https://web.archive.org/web/20090528082618/http://www.cforcoding.com/2009/05/orm-or-sql.html
  5. https://blog.codinghorror.com/object-relational-mapping-is-the-vietnam-of-computer-science/