Month: 六月 2018

软件工程中的 “3” 的规则

我注意到了一个神奇的软件工程法则:在你正确地解决问题之前,你至少需要3个例子。

具体说来是这样的:

  1. 不要试图在两个类之间共享代码,至少等到你有三个类的时候。
  2. 解决问题的前两次尝试一定会失败,因为你还没完全理解这个问题。第三次才行
  3. 任何想要早期就能设计好的尝试都会导致对于巧合情形的过度拟合。

你在说什么?请给个例子

比如说你在实现一个类,从银行抓取数据。下面是一个非常傻瓜的版本,但是应该说明了问题:

class ChaseScraper:
    def __init__(self, username, password):
        self._username = username
        self._password = password

    def scrape(self):
        session = requests.Session()
        sessions.get("https://chase.com/rest/login.aspx",
                 data={"username": self._username,
                   "password": self._password})
    sessions.get("https://chase.com/rest/download_current_statement.aspx")

现在你想添加第二个类 CitiBankScraper 来实现相同的接口,但是改变了一些实现细节。实际上假设CitiBank只是有一个不同的 url 和表单元素名称而已。让我们来添加一个新的爬虫:

class CitibankScraper:
    def __init__(self, username, password):
        self._username = username
        self._password = password

    def scrape(self):
        session = requests.Session()
        sessions.get("https://citibank.com/cgi-bin/login.pl",
                 data={"user": self._username,
                   "pass": self._password})
        sessions.get("https://citibank.com/cgi-bin/download-stmt.pl")

因为经过了多年DRY原则的教育,这时候我们发现这两个类的代码几乎是重复的!我们应该重构一下,把所有的重复代码都放到一个基类中。在这里,我们需要Inserve of Control 模式,让基类来控制逻辑。

class BaseScraper:
    def __init__(self, username, password):
        self._username = username
        self._password = password

    def scrape(self):
        session = requests.Session()
        sessions.get(self._LOGIN_URL,
                 data={self._USERNAME_FORM_KEY: self._username,
                   self._PASSWORD_FORM_KEY: self._password})
        sessions.get(self._STATEMENT_URL)


class ChaseScraper(BaseScraper):
    _LOGIN_URL = "https://chase.com/rest/login.aspx"
    _STATEMENT_URL = "https://chase.com/rest/download_current_statement.aspx"
    _USERNAME_FORM_KEY = "username"
    _PASSWORD_FORM_KEY = "password"


class CitibankScraper(BaseScraper):
    _LOGIN_URL = "https://citibank.com/cgi-bin/login.pl"
    _STATEMENT_URL = "https://citibank.com/cgi-bin/download-stmt.pl"
    _USERNAME_FORM_KEY = "user"
    _PASSWORD_FORM_KEY = "pass"

这应该让我们删掉了不少代码。这已经是最简单的方法之一了。所以问题在哪里呢?(出去我们实现继承的方法不好之外)

问题是我们过度拟合了!过度拟合是什么意思呢?我们正在抽象出并不能很好泛化的模式!

facepalm

为了验证这一点,假设我们又需要从第三个银行抓取数据。也许它需要如下几点:

  • 他需要两步验证
  • 密码是使用 JSON 传递的
  • 登录使用了POST而不是GET
  • 需要同时访问多个页面
  • 要访问的url是根据当前日期动态生成的

…… 或者随便什么东西,有1000中方式让我们的代码不能工作。我希望你已经感觉到问题所在了。我们以为我们通过前两个爬虫发现了一个模式!然鹅悲剧的是,我们的爬虫根本不能泛化到第三个银行(或者更多,第n个)。也就是说,我们过拟合了。

过拟合到底是什么意思?

过拟合指的是我们在数据发现了一个模式,但是这个模式并不能很好地泛化。当我们在写代码的时候,我们经常对于优化代码重复非常警觉,我们会发现一些偶然出现的模式,但是如果我们查看整个程序的话,我们知道这些模式可能并不能很好地代表整个程序的模式。所以当我们实现了两个银行的爬虫之后,我们以为我们发现了一个广泛的模式,实际上并不是。

注意到,代码重复并不总是一件坏事。工程师们通常过分关注减少重复代码,但是也应该注意区分偶然的代码重复和系统性的代码重复之间的区别。

因此,让我来引入第一个 “3” 之规则。如果你只有两个类或者对象,不要过分关注代码重复。当你在三个不同的地方看到同一个模式的时候在考虑如何重构。

“3” 之规则应用到架构上

同样的推理可以应用到系统设计上,但是会得出一个非常不同的结论。当你从头构建一个新的系统的时候,你不知道他最终会被如何使用,不要被假设所限制。在第一代和第二代产品上,我们认为需要的限制可能真的是需要的,但是当实现第三代产品的时候,我们会发现假设是完全错误的,并最终实现正确的版本。

比如说,Luigi就是解决问题的第三次尝试。前两个尝试解决了错误的问题,并且为错误的方向做了优化。比如第一个版本依赖于在 XML 中设计依赖图。但是很显然这是非常不友好的,因为你一般县要在代码里生成依赖图比较好。而且,在前两次设计中看起来很有用的一些新设计,比如任务解耦输出,最终只给一些非常少见的例子添加了支持,但是有添加了不少复杂度。

第一个版本中看起来很奇怪的问题可能在后来是很重要的问题,反过来也是。

I was reminded of this when we built an email ingestion system at Better. The first attempt failed because we built it in a poor way (basically shoehorning it into a CRUD request). The second one had a solid microservice design but failed for usability reasons (we built a product that no one really asked for). We’re halfway through the third attempt and I’m having a good feeling about it.

这个故事告诉了我们第二个 “3” 之规则——在系统设计上,直到第三次你才能够做对。

更重要的是,如果你的第一版有一些奇怪的位置问题,不要假设你需要搞定他们。走捷径。绕开奇怪的问题。估计你也不会运行这个系统很长时间——总有一天他会坏的。第二个版本大多数时候也是坏的。第三个版本值得你把它雕琢到完美。

three cupcakes

原文:https://erikbern.com/amp/2017/08/29/the-software-engineering-rule-of-3.html

curio asks 源码解析

asks 是 Python 的异步框架 curio 中的 一个 http 库。它基于 h11 这个库来做 http 协议的解析,然后提供了在 curio 下的 IO 操作。下面按照功能逐个介绍其中的每个部分。

杂项

auth.py

该文件中主要包含了 http auth 相关函数, 支持了 Basic Auth 的 Digest Auth。值得注意的是,digest auth 作为一种既很复杂又不安全的认证方式,已经没有人用了。如果需要使用 http auth 的话,现在推荐的方式使用 https + basic auth。

base_funcs.py

提供了一些快捷方式函数,比如 curio.get。

cookie_utils.py

该文件主要包含了 CookieTracker, 对外的方法主要有两个 get_additional_cookies 用于获取域名对应的 cookie,_store_cookies 用于添加 cookie。

parse_cookies 函数主要用于解析 set-cookie 头部,并把解析到的 cookie 附加到 response 对象上。

errors.py

asks 中抛出的异常的类

http_utils.py

处理编码和压缩的两个函数。

请求与响应

request_object.py

该文件中主要是定义了 RequestProcessor 类。RequestProcessor 用于生成一个 HTTP 请求。

makerequest 方法。hconnection定义和使用的地方相距太远了。cookie的生成应该使用join。之后调用 _requestio 发送请求

_request_io 调用 首先掉用 _send, 然后调用 _catch_response

_catch_response 调用 recv_event

_recv_event 不断调用 _async_lib.recv(self.sock, 10000) 从而不断产生数据,知道读完为之

sessions.py

session 类

request 调用 grabconnection 获取一个socket,然后把这个socket交给Request对象
grab
connection 调用 checkoutconnection 获得一个socket或者,调用makeconnection产生一个新的socket,注意其中有一个奇怪的 await sleep(0),可能意思是把循环交回给event loop

make_connection 调用 _connect 方法,并把host和port作为属性写到socket上

session 中有两个SocketQ的类,connpool, checkedout_sockets 分别用来保存已连接未使用的socket和正在使用中的socket

response_objects.py

Response 表示了一个响应。如果在发起请求的时候选择了 stream=True, response.body 会是一个 StreamBody 实例,StreamBody 用于流式处理响应。

Cookie 类表示了一个 Cookie,不知道这里为什么没有用标准库的 cookie。

Connection Pool

如果使用代理的话

req_structs.py

SocketQ 是一个 socket 的连接池。使用一个 deque 作为存储,实际上相当于又模拟了一个字典 {netloc => socket}(思考:为什么不使用OrderedDict呢?)index 返回指定 hostloc 对应的 index。pull 弹出指定 index 的 socket。__contains__ 遍历看是否包含对应的socket。需要注意的是这个类不是线程安全的,不过对于 curio 来说,线程安全似乎无关紧要,毕竟只有一个线程。

CaseIncesitiveDict 是一个对大小写不敏感的词典,直接从 requests 里面拿过来的。

curio 的网络通信

首先,需要引入curio.socket 而不是使用内置的socket

TCP通信,使用 sock.bind/listen/accept 等建立服务器,使用recv和sendall发送接收消息。
UDP通信,使用recvfrom和sendto函数通信

作为客户端使用 curio.open_connection 打开到服务器的链接,其中 ssl=True打开的是HTTPS连接诶

对于其他要使用ssl的情况,应该使用curio.ssl而不是标准库的ssl

curio.network.

ssl.wrapsocket 不支持serverhostname sslcontext.wrap_socket 支持

不要把 proxy 传递给 request 对象

添加 http 代理支持

asks 把繁重的 http 解析工作都用 h11 这个库巧妙的解决了,所以 asks 本身是非常轻量的一个库。很遗憾的是,在 asks 中尚未支持代理,下面我们尝试为 asks 增加 http 代理的支持 😛

在 http 代理模式中,每个请求都需要添加上 Proxy-Authorization 的 Header。而在 https 请求中,只有 Connect 的时候需要添加上 Proxy-Authorization 的 Header。

代理的 socket 池和真正的 socket 池要分开,这样设计还简单一点。

Python 转义 html 实体字符

在网页中经常出现 <, &amp;, &0x0026; 这些特殊字符,这是 html 实体字符转义,用于防止 XSS 攻击。Python3 标准库中包含了 html.entities 模块,可以用于转义和反转义这些字符。

html.entities.entitydefs 中包含了名称到符号的映射比如{"amp": "&"}
html.entities.name2codepoint 中包含了名称到数字的映射比如 {"amp": 0x0026}
html.entities.codepoint2name 中包含了数字到名称的映射比如 {0x0026: "amp"}

Python 中的 queue 模块

在 Python 中多进程或者多线程之间通信可以使用队列,标准库中实现了一个线程安全的库 queue.Queue,和进程安全的库 multiprocessing.Queue

There are 3 kind of queues: `Queue LifoQueue HeapQueue

q = Queue(size)

get(block=True) return a object
get_nowait()
put(item, block=True) put a object

qsize()
empty()
full()

task_done() # indicate one item process finished raise ValueError
join() # wait until all item processed

Queue.Empty
Queue.Full

How to iterate a queue?

by using iter(q, None), note that you have to put a sentinel value manually

Queue vs multiprocessing.Queue

despite their similar api, their implementation is completely different

http://stackoverflow.com/questions/925100

如果在一个线程使用了异步代码,那么所有的操作都必须使用异步操作,但是并不是所有的操作都需要或者能够使用异步操作。

在异步线程和同步线程之间分享数据需要使用一个共用的 queue

如果需要把异步操作分布式不熟使用,在异步的事件循环之间分享数据也需要使用一个 queue

阅读《Python hitchhiker》笔记

Package

A file modu.py in the directory pack/ is imported with the statement import pack.modu. This statement will look for an __init__.py file in pack, execute all of its top-level statements. Then it will look for a file named pack/modu.py and execute all of its top-level statements. After these operations, any variable, function, or class defined in modu.py is available in the pack.modu namespace.

A commonly seen issue is to add too much code to __init__.py files. When the project complexity grows, there may be sub-packages and sub-sub-packages in a deep directory structure. In this case, importing a single item from a sub-sub-package will require executing all __init__.py files met while traversing the tree.

This and other issues led to the idea that using stateless functions is a better programming paradigm.

Another way to say the same thing is to suggest using functions and procedures with as few implicit contexts and side-effects as possible. A function’s implicit context is made up of any of the global variables or items in the persistence layer that are accessed from within the function. Side-effects are the changes that a function makes to its implicit context. If a function saves or deletes data in a global variable or in the persistence layer, it is said to have a side-effect.

Pure functions

  • Pure functions are deterministic: given a fixed input, the output will always be the same.
  • Pure functions are much easier to change or replace if they need to be refactored or optimized.
  • Pure functions are easier to test with unit-tests: There is less need for complex context setup and data cleaning afterwards.
  • Pure functions are easier to manipulate, decorate, and pass around.

However, it may be a good discipline to avoid assigning to a variable more than once, and it helps in grasping the concept of mutable and immutable types.

  • A LICENSE file should always be present and specify the license under which the software is made available to the public.
  • A TODO file or a TODO section in README should list the planned development for the code.
  • A CHANGELOG file or section in README should compile a short overview of the changes in the code base for the latest versions.
  • Project Publication

Depending on the project, your documentation might include some or all of the following components:

  • An introduction should show a very short overview of what can be done with the product, using one or two extremely simplified use cases. This is the thirty-second pitch for your project.
  • A tutorial should show some primary use cases in more detail. The reader will follow a step-by-step procedure to set-up a working prototype.
  • An API reference is typically generated from the code (see docstrings). It will list all publicly available interfaces, parameters, and return values.
  • Developer documentation is intended for potential contributors. This can include code convention and general design strategy of the project.

put project/ tests/ docs/ directory in side the project

Python functools 中有用的一些函数

functools

partial(fn, args, *kwargs)

lru_cache

Decorator to wrap a function with a memoizing callable that saves up to the maxsize most recent calls. It can save time when an expensive or I/O bound function is periodically called with the same arguments.
Since a dictionary is used to cache results, the positional and keyword arguments to the function must be hashable.
If maxsize is set to None, the LRU feature is disabled and the cache can grow without bound. The LRU feature performs best when maxsize is a power-of-two.
If typed is set to true, function arguments of different types will be cached separately. For example, f(3) and f(3.0) will be treated as distinct calls with distinct results.
To help measure the effectiveness of the cache and tune the maxsize parameter, the wrapped function is instrumented with a cacheinfo() function that returns a named tuple showing hits, misses, maxsize and currsize. In a multi-threaded environment, the hits and misses are approximate.
The decorator also provides a cache
clear() function for clearing or invalidating the cache.

singledispatch

To define a generic function, decorate it with the @singledispatch decorator. Note that the dispatch happens on the type of the first argument, create your function accordingly

@singledispatch
def fun()
    pass

@fun.register(int)
def fun_int()
    pass

to get the dispatched func. use fun.dispatch(type)

Elasticsearch 上手教程

ES 家的几个产品版本不太统一,有的在 2.x,有的在 4.x,为了打包在一起卖,ES 家把 ES、Kibana、Logstash 的版本统一成了 5.0 版本。现在的版本是 7.x

版本

  • 关系型数据库:Databases -> Tables -> Rows -> Columns
  • ElasticSearch:Indices -> Types -> Documents -> Fields

ElasticSearch 集群可以包含多个索引 (indices),每一个索引可以包含多个类型 (types),每一个类型包含多个文档 (documents),然后每个文档包含多个字段 (Fields)。

但是需要特别注意的是:在 SQL 中,不同表中的列都是毫不相关的,而在 ES 中,同一个索引内,不同 type 的同名字段就是同一个字段

  • 索引(名词)如上文所述,一个索引 (index) 就像是传统关系数据库中的数据库,它是相关文档存储的地方,index 的复数是 indices 或 indexes。
  • 索引(动词)「索引一个文档」表示把一个文档存储到索引(名词)里,以便它可以被检索或者查询。这很像 SQL 中的 INSERT 关键字,差别是,如果文档已经存在,新的文档将覆盖旧的文档。
  • 倒排索引,传统数据库为特定列增加一个索引,例如 B-Tree 索引来加速检索。Elasticsearch 和 Lucene 使用一种叫做倒排索引 (inverted index) 的数据结构来达到相同目的。

在 Elasticsearch 中,每一个字段的数据都是默认被索引的。也就是说,每个字段专门有一个反向索引用于快速检索。一个文档不只有数据。它还包含了元数据 (metadata)——关于文档的信息。三个必须的元数据节点是:

_index  文档所在的索引
_type   文档代表的对象的类
_id 文档的唯一标识
_version    用于控制冲突,可以由外部指定,采用乐观锁

检索返回 _source,可以使用 _update API 局部更新文档,_mget 总会返回 200

安装

因为 AWS 这些云厂商一直在吸开源血,所以 ES 默认产品现在需要使用自己的 brew tap 安装:

brew tap elastic/tap
brew install elastic/tap/elasticsearch-full

创建索引和插入数据

Mapping 用来定义 ES 中文档的字段类型,如果使用 dynamic mapping, ES 就会在第一次见到某个字段的时候推断出字段的类型。这时候就会有问题了,比如说时间戳可能被推断成了 long 类型。

所以,一般我们会在创建索引的时候指定 mapping 的类型。

PUT http://localhost:9200/company

{
  "settings": {
    "number_of_shards": 1,
    "number_of_replicas": 1,
    // 指定文本分词
    "analysis": {
      "analyzer": {
        "analyzer-name": {
          "type": "custom",
          "tokenizer": "keyword",
          "filter": "lowercase"
        }
      }
    },
  },
  // 指定字段的类型
  "mappings": {
    "properties": {
      "age": {
        "type": "long"
      },
      "experienceInYears": {
        "type": "long"      
      },
      "name": {
        "type": "text",
        "analyzer": "analyzer-name"  // 或者直接指定 ik_smart
      }
    }
  }
}

ES 中的类型有:boolean, binary, long, double, text, date 等。

  • 如果需要存储 enum 类型的话,直接在 text 类型中指定 {“index”: False} 就好了
  • date 类型可以自动识别一些日期格式,比如时间戳,YYYY-mm-dd 等,也可以使用 format 指定,epoch_millis, epoch_second 用来指时间戳。
  • 使用 analyzer 指定分词器

插入文档

POST http://localhost:9200/company/employee/?_create

{
  "name": "Andrew",
  "age" : 45,
  "experienceInYears" : 10
}

Text Analyzer

众所周知,倒排索引的第一步就是要对文本进行一些预处理,尤其是分词。英文还好说,天然就是分好的,而中文则需要一些特殊的处理。虽然英文分词比较简单,但是因为会有词形的变化,所以还需要归一化。在 ES 中负责这些工作的部分叫做 Text Analyzer.

Text Analyzer 一般分为三个部分:

  1. Char Filter, 也就是处理一些字符
  2. Tokenizer, 也就是分词器
  3. Token Filter, 也就是处理一些词。添加同义词,抽取词干也会在这里进行

当文件被添加到索引和查询索引的时候都会调用 text analyzer.

为某个字段指定 text analyzer:

PUT my-index-000001
{
  "mappings": {
    "properties": {
      "title": {
        "type": "text",
        "analyzer": "whitespace"
      }
    }
  }
}

为某个索引指定 text analyzer:

PUT my-index-000001
{
  "settings": {
    "analysis": {
      "analyzer": {
        "default": {
          "type": "simple"
        }
      }
    }
  }
}

使用 IK 分词

最常用的中文分词工具就是 IK 分词了,在 GitHub 上已经有一万个 Star 了,应该还是值得信任的。

elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.8.1/elasticsearch-analysis-ik-7.8.1.zip

其中的版本号需要替换成对应的 ES 的版本号。

IK 为 Elasticsearch 增加了两个分词器:ik_smart, ik_max_word. 其中 ik_smart 会分出较少的词,而 ik_max_word 会穷尽每一种方法分出尽量多的词。

比如说:今天天气真好.

  • ik_smart 会分成:今天天气, 真好.
  • ik_max_word 会分成:今天天气, 今天, 天天, 真好.

搜索

GET /_search

GET /bank/_search
{
  "query": { "match_all": {} },  // 查询的条件
  "sort": [
    { "account_number": "asc" }  // 排序条件
  ],
  "from": 10,  // 用于分页
  "size": 10   // 每页大小
}

返回

{
  "took" : 63,  // 检索花费的时长
  "timed_out" : false, // 是否超时
  "_shards" : {  // 关于检索的分片的信息
    "total" : 5,
    "successful" : 5,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {  // 命中结果
    "total" : {  // 命中结果数量
        "value": 1000,
        "relation": "eq"
    },
    "max_score" : null,
    "hits" : [ {
      "_index" : "bank",
      "_type" : "_doc",
      "_id" : "0",
      "sort": [0],
      "_score" : null,
      // 检索到的文档内容
      "_source" : {"account_number":0,"balance":16623,"firstname":"Bradshaw","lastname":"Mckenzie","age":29,"gender":"F","address":"244 Columbus Place","employer":"Euron","email":"bradshawmckenzie@euron.com","city":"Hobucken","state":"CO"}
    }, {
      "_index" : "bank",
      "_type" : "_doc",
      "_id" : "1",
      "sort": [1],
      "_score" : null,
      "_source" : {"account_number":1,"balance":39225,"firstname":"Amber","lastname":"Duke","age":32,"gender":"M","address":"880 Holmes Lane","employer":"Pyrami","email":"amberduke@pyrami.com","city":"Brogan","state":"IL"}
    }, ...
    ]
  }
}

搜索中有很多参数可以调节:

  1. track_total_hits. 默认情况下只有小于 10000 的时候结果才是精确的,因为统计有多少结果是一个 O(n) 的操作。
  2. filter. 按照某些条件过滤结果。比如电商中,搜索衣服时候的尺码颜色等
  3. highlighter. 在搜索结果中节选出包含关键词的部分
  4. _source. 通过一个数组指定返回的字段。默认情况下是返回所有字段的。

查询字段

最简单的查询:

GET /_search
{
  "query": {
    "match": {
      "message": "this is a test"
    }
  }
}

默认情况下,查询的字段是使用 OR 关系的,显然这不是我们想要的,可以指定为 and

GET /_search
{
  "query": {
    "match": {
      "message": {
        "query": "this is a test",
        "operator": "and"
      }
    }
  }
}

match_all 用来读取所有文档:

GET /_search
{
    "query": {
        "match_all": {}
    }
}

搜索结果翻页

使用 from 和 size 两个参数可以翻页,但是这两个参数就和 SQL 里面的 limit 和 offset 一样,页码越大,性能越低,因为他们其实就是傻乎乎的弄出所有结果来然后取中间。

除此之外,还可以使用 scroll api, 也就是让 ES 缓存住这个查询结果,每次都取一段。显然对于 ad hoc 的用户搜索来说也是不适用的。

最后一种方法是使用 search_after, 其实就相当于 SQL 中使用 where id > $id, 然后每次查询都用上次的最大 ID 就可以了。在 ES 中自然是取 sort 字段中每次查询的最大(最小)值。

GET my-index-000001/_search
{
  "size": 10,
  "query": {
    "match" : {
      "message" : "foo"
    }
  },
  "search_after": [1463538857, "654323"],  // 对应 sort 中的字段
  "sort": [
    {"@timestamp": "asc"},
    {"tie_breaker_id": "asc"}
  ]
}

搜索结果排序

默认情况下,搜索结果会按照计算出来的 _score 也就是和搜索 query 的相关度来排序,我们也可以通过自定义 sort 字段来指定排序规则。

GET /my-index-000001/_search
{
  "sort" : [
    { "post_date" : {"order" : "asc"}},
    "user",
    { "name" : "desc" },
    { "age" : "desc" },
    "_score",
    "_doc",
  ],
  "query" : {
    "term" : { "user" : "kimchy" }
  }
}

查询 DSL

ES 用 JSON 实现了自己的一套查询语句,基本上就是个 AST, 直接写就行了。子句分成两个:

  1. 查询子句
  2. 复合子句

设置密码

首先确保你安装的是 ES 的完全版,而不是 OSS 版本,不然是没有 xpack 的。然后在 elasticsearch.yml 中增加:

xpack.security.enabled: true

如果是在 Mac 上,可以通过 brew info elasticsearch-full 来查看配置文件的路径。

然后执行 bin/elasticsearch-keystore add "bootstrap.password"elasticsearch-setup-passwords interactive

这时候只有再使用密码才能够接着访问:

curl --user elastic:123456 localhost:9200

Python 客户端

基础使用

pip install elasticsearch

需要注意的是大版本要和使用的 elasticsearch 服务器对应。

from elasticsearch import Elasticsearch

hosts = ['https://user:secret@localhost:443']
es = Elasticsearch(hosts, sniff_on_start, sniff_on_failure, sniffer_timeout=60)

es 的实例是线程安全的,可以在多个线程之间共享。另外需要注意的是,对于长时间运行的脚本,最好开启 sniff, 这样可以适应集群的变化。

全局配置

忽略错误

可以通过 ignore 参数指定忽略一些错误,不过最好不要这么搞,还是显式地使用 try 比较清晰明了。

es.indices.create(index='test-index', ignore=400)
es.indices.delete(index='test-index', ignore=[400, 404])

过滤结果

通过使用 filter_path 参数可以过滤结果。ES 返回的结果本来就很冗余,这个参数还是很有用的。* 表示通配符

es.search(index='test-index', filter_path=['hits.hits._id', 'hits.hits._type']) # returns the _id and _type
es.search(index='test-index', fitler_path=['hits.hits._*']) # returns all fileds in hits

常用查询方法

es.method(index=”, doc_type=”, id=”, zbody=”, _source=True/False…)

es.count(body=None, index=None, …)

返回匹配一个 query 的文档的数量

es.create(index, id, body, doc_type=None, …)

插入一个新的文档,参数名字也都挺明确的。

es.update(index, id, body, doc_type=None)

根据一个 id 更新文档

es.index(index, id, body, doc_type=None, …)

相当于 create or update

es.delete(index, id, doc_type=None)

根据 id 删除一个文档

es.exists(index, id, …)

根据 id 判断一个文档是否存在。

es.get(index, id, _source=True, …)

根据 id 返回一个文档。

es.search(body=None, index=None, source=True, from=x, size=10,)

最核心的方法了,搜索文档

索引管理方法

通常通过 es.indices 属性来访问索引相关的方法

es.indices.analyze(body=None, index=None,…)

使用索引指定的分词器对文本进行分析。

es.indices.create(index, body=None,…)

创建一个索引的 mapping

es.indices.delete(index…)

删除一个索引

es.indices.get_mapping(index)

返回一个索引的 mapping

es.indices.put_mapping(index, body, …)

更新一个索引的 mapping

批量操作

在 es 的 helpers 中提供了一个 bulk, 也就是批量的 API 用于批量操作。语法是:

from elasticsearch.helpers import bulk, parallel_bulk 

bulk(es, actions)

其中 es 是一个 es 的客户端,而 actions 是一个数组,或者 iterable, 其中的每个元素是:

{
  "_op_type": "delete", // index/create/delete/update, 默认是 index
  "_index": "index-name",  // 索引名字
  "_id": 42,
  "doc": {...}
}

异常

一般情况下,直接 catch 住基类错误就行了

  • ImproperlyConfigured
  • ElasticsearchException
    • SerializationError, json 序列化错误
    • TransportError
    • ConnectionError
    • NotFoundError, 文档不存在,对应 404
    • ConflictError, 文档和缩影冲突,对应 409
    • RequestError, 对应 400
    • AuthenticationError, 对应 401
    • AuthorizationError, 对应 403

参考

  1. http://www.tianshangkun.com/2018/05/15/ElasticSearch%E7%9A%84%E6%90%AD%E5%BB%BA%E4%B8%8E%E6%95%B0%E6%8D%AE%E7%BB%9F%E8%AE%A1/
  2. https://segmentfault.com/a/1190000017215854
  3. https://www.elastic.co/blog/found-elasticsearch-mapping-introduction
  4. https://medium.com/@ashish_fagna/getting-started-with-elasticsearch-creating-indices-inserting-values-and-retrieving-data-e3122e9b12c6
  5. https://elasticsearch-py.readthedocs.io/en/master/api.html
  6. https://www.elastic.co/blog/phrase-Queries-a-world-without-stopwords
  7. https://www.elastic.co/guide/en/elasticsearch/reference/current/built-in-users.html#set-built-in-user-passwords
  8. https://stackoverflow.com/questions/16712642/elasticsearch-enum-field
  9. smartcn vs ik

头条大讲堂——短视频与机器学习

机器学习需要 Ground Truth
modality 是什么意思。。。

浅度学习

短视频如何火

为什么:大多数的短视频 1 天之后都销声匿迹了
数据集:10 个 vine 用户的 follower 广度优先,获得十万种子用户

大 V 发布的 (social), 声音很好 (audio), 美感很好 (visual), #hashtag 选的好 (text)

从以上四个方面抽取特征。

social 的影响最大

预测地理标签

1.2%才有地理标签,预测是会议室/篮球场/公园等,最重要的是 visual feature

深度学习

声音相关

freesound 是一个免费的声音库。从一个标签开始,抽不同的标签的声音。查找不同的声音

深度学习加迁移学习

alexnet-7 是啥

sequential and sparse

字典学习是啥?

什么事共空间?

未来

用户的兴趣是动态的,但是推荐却是固定的。

无法搜索没有文字的视频 比如:可以截一帧然后到 google 上搜索

用户的平台对应关系,利用 quora 等,用户自己关注了

ontology

Ø 短视频的评论也是文本信息啊
Ø Q: 短视频和长视频的分析区别
Ø A: follow 等,地理属性,质量低

《算法设计与分析基础》笔记

http://blog.csdn.net/wangyunyun00/article/details/23464359

差值查找

折半查找这种查找方式,还是有改进空间的,并不一定是折半的!

mid = (low+high)/ 2, 即 mid = low + 1/2 * (high - low);

改进为下面的计算机方案(不知道具体过程):

mid = low + (key - a[low]) / (a[high] - a[low]) * (high - low),

也就是将上述的比例参数 1/2 改进了,根据关键字在整个有序表中所处的位置,让 mid 值的变化更靠近关键字 key,这样也就间接地减少了比较次 数。

Andrew Ng 的公开课笔记

Andrew ng 课程的 python 解法

http://www.johnwittenauer.net/machine-learning-exercises-in-python-part-1/

第一课

拟合曲线也是机器学习

ICA independent component analysis 用于解决鸡尾酒宴会问题(分离独立声源)

梯度下降法可以有两种

  • Batch,批量梯度下降
  • Stochastic,随机梯度下降