Author: 逸飞

React Hooks

使用 useState hook

useState 可以用来管理一个组件比较简单的一两个状态,如果状态多了不适合使用 useState 管理,可以使用 useReducer.

import React, {useState} from 'react';

function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}> Click me </button>
    </div>
  );
}

export default Counter;

在上面的简单例子中,我们直接使用值作为 setState 的参数,但是在部分更新 state 的时候,最好使用一个函数来作为参数:

const [state, setState] = useState({"showText": true, "showShadow": true})

<button onClick={() => setState((s) => {...s, showText: false})}>Hide Text</button>

使用 useEffect hook

useEffect hook 用来实现一些副作用,一般可以用作页面首次加载时读取数据。

import React, {useState, useEffect} from 'react';

function App() {
  const [isOn, setIsOn] = useState(false);

  useEffect(() => {
    let interval;
    if (isOn) {
      interval = setInterval(() => console.log('tick'), 1000);
    }
    return () => clearInterval(interval);
  }, [isOn]);
  ...
}

export default App;

在 useEffect 中返回的函数会被用来做垃圾清理。另外需要注意的是,初始化的时候总会触发一次 useEffect.

默认情况下,每次 state 有改变的时候,都会调用 useEffect 函数。如果需要更改触发的时机,那么需要使用 useEffect 的第二个参数来指定监听的事件或者说状态。当第二个参数只使用一个空数组 [] 的时候就只会在组件加载的时候调用。数组中有哪些变量,表示在这些变量变化的时候调用。

使用 useContext hook

Context 用来向所有包含的元素广播状态,或者说事件,而不需要通过组件树层层传递。

  1. 首先需要通过 React.createContext 定义一个高层次 Context,
  2. 然后在最外层使用 <MyContext.Provider /> 来包裹需要接受这个 context 的所有组件。
  3. 在需要使用状态的元素中调用 const ctx = useContext(MyContext), 然后使用 ctx 访问 Context 中的值

在基于类的 React 组件中,需要使用 <MyContext.Consumer /> 来实现读取值,现在我们都用 useContext 钩子了。

const themes = {
  light: { foreground: "#000000", background: "#eeeeee" },
  dark: { foreground: "#ffffff", background: "#222222" }
};

const ThemeContext = React.createContext(themes.light);

function App() {
  return (
    <ThemeContext.Provider value={themes.dark}>
      <Toolbar />
    </ThemeContext.Provider>
  );
}

function Toolbar(props) {
  return (
    <div> <ThemedButton /> </div>
  );
}

function ThemedButton() {
  const theme = useContext(ThemeContext);
  return (
    <button style={{ background: theme.background, color: theme.foreground }}>
      I am styled by theme context!
    </button>
  );
}

使用 useReducer hook

useReducer 和 useState 的用途基本是一样的,但是当需要的状态比较复杂的时候,最好使用 useReducer. 有了 useReducer 钩子,基本上可以不使用 redux 了。

const [state, dispatch] = useReducer(reducer, initialArg, init);

useReducer 通常放在管理一组状态的根元素这个层级,比如说一个页面。dispatch 函数触发事件,reducer 函数用来处理事件,更新 state.

需要注意的是,在 useReducer 的 reducer 函数中,和 redux 不同的是,不需要 state=initialState 这个参数。默认参数在调用 useReducer 的时候已经给出了。

const initialState = {count: 0};

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return {count: state.count + 1};
    case 'decrement':
      return {count: state.count - 1};
    default:
      throw new Error();
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
    </>
  );
}

useReducer + useContext = (Better) Redux

可以组合使用 useReducer 和 useContext 来实现 Redux 的功能。对于某个组件自身的数据,我们只使用 useReducer 就可以很好地管理了。然而对于多个组件都需要使用的全局数据,需要是用 useContext 来广播给所有需要该数据的组件。

两者的组合使用很简单,使用 Context.Provider 把 state 和 dispatch 这两个变量广播给 root 下的所有元素,这样在需要使用 state 和 dispatch 的地方直接 useContext(Context) 就好了。

useMemo/useCallback 钩子

用来避免重复计算或者重复生成函数。

useRef 钩子

https://stackoverflow.com/questions/56455887/react-usestate-or-useref

自定义钩子

通过灵活组合 useState, 和 useEffect, 我们完全可以创建自己的钩子。

import React from 'react';

function useOffline() {
  const [isOffline, setIsOffline] = React.useState(false);

  function onOffline() {
    setIsOffline(true);
  }

  function onOnline() {
    setIsOffline(false);
  }

  React.useEffect(() => {
    window.addEventListener('offline', onOffline);
    window.addEventListener('online', onOnline);

    return () => {
      window.removeEventListener('offline', onOffline);
      window.removeEventListener('online', onOnline);
    };
  }, []);

  return isOffline;
}

function App() {
  const isOffline = useOffline();

  if (isOffline) {
    return <div>Sorry, you are offline ...</div>;
  }

  return <div>You are online!</div>;
}

export default App;

参考

  1. https://www.robinwieruch.de/react-hooks
  2. https://www.robinwieruch.de/react-hooks-fetch-data
  3. https://medium.com/@nazrhan.mohcine/react-hooks-work-with-usestate-and-usereducer-effectively-471646cdf925
  4. https://swizec.com/blog/usereducer-usecontext-for-easy-global-state-without-libraries
  5. https://medium.com/@wisecobbler/using-a-function-in-setstate-instead-of-an-object-1f5cfd6e55d1
  6. https://stackoverflow.com/questions/56615931/react-hook-setstate-arguments

Nextjs 教程

当我们要写一个稍微复杂的 React 应用的时候,就需要路由功能了,比较流行的路由是 react router. 这是一个很好的库,但是当我们已经用到路由的时候,下一步就该考虑如何做服务端渲染了,所以直接上 next.js 吧。

鉴于我已经使用 create-react-app 创建了 react 应用,需要手工安装一下 next.js

yarn add next

把 package.json 中的 scripts 替换掉

"scripts": {
  "dev": "next dev",
  "build": "next build",
  "start": "next start"
}

核心概念

next 的核心概念是页面,没啥可解释的吧。按照约定,放在 /pages 文件夹中的每一个组件都是一个页面,比较恶心的是每个组件需要使用 export default 导出。

当然,pages/index.js 对应的自然是首页了。

function HomePage() {
  return <div>Welcome to Next.js!</div>
}

export default HomePage

然后就可以看到首页啦!啊啊啊

next.js 中,完全按照文件的物理路径来确定路由,比如如果你需要 post/1 这种路径,直接定义 pages/post/[id].js, 也是够直接了。

读取 URL 参数

需要使用 router 来手动读取

import {useRouter} from 'next/router'

function MyPage() {
  const router = useRouter();
  const {keyword} = router.query;
}

获取数据

在 nextjs 中,鼓励的方式是在服务端编译或者渲染的时候获取数据,而不是由客户端渲染数据。这里我们先不看 SSG 了,看现在最需要的 SSR.

在一个页面中,export 一个 async 函数 getServerSideProps 就可以实现获取服务端的数据。

export async function getServerSideProps(context) {
  return {
    props: {}, // will be passed to the page component as props
  }
}

context 中比较重要的几个属性:

  • params 路径中的参数,比如 {id: xxx}
  • req/res 请求响应
  • query query_string

在这个函数中,应该直接读取数据库或者外部 API.

除此之外,另一种方式自然是传统的在客户端获取数据了,可以使用 useSWR 库。

样式

next.js 中默认不让导入全局的 CSS, 所以你必须在 pages/_app.js 中导入全局的 css.

import '../styles.css'

// This default export is required in a new `pages/_app.js` file.
export default function MyApp({ Component, pageProps }) {
  return <Component {...pageProps} />
}

对于每一个组件,把它们的样式文件放到 [name].module.css 中就好啦。然后需要导入

import styles from 'Button.module.css';

export default function Button() {
  return <div className={styles.button}>Button</div>
}

另一种方式是使用 styled-jsx, 也就是把 CSS-in-JS 的方式,我个人还是喜欢这种方式一些。但是这种不好在 VSCode 中直接显示调色板。

<style jsx>{`
  h1 {
    color: red;
  }
`}</style>

静态文件

也很简单,直接放到 /public 目录,然后就能在根路径访问了。

路由

使用 [xxx] 放在路径中作为参数就好了。

nextjs 中的链接是这样的:

<Link href="/blog/[slug]" as={`/blog/${post.slug}`}>
    <a>{post.title}</a>
</Link>

外部接口

错误页面

next.js 可以自定义 404 和 500 错误页面

参考

  1. https://haodong.io/render-client-side-only-component-in-next-js
  2. https://github.com/vercel/next.js/blob/canary/examples/progressive-render/pages/index.js

《Prometheus 监控实战》笔记

第一章

与安全性一样,监控也应该是应用程序的核心功能。

如果应用程序在你没有注意到的情况下发生了故障,那么及时进行了监控,你也需要考虑下正在监控的内容是否合
理。

应该监控业务事务的内容或速率,而不是监控它运行的 Web 服务器的运行时间。

静态阈值几乎总是错误的,比如说只监控 CPU 超过 80% 的情况

监控的两种机制:

  • 探针,当我们对要监控的资源没有控制权的时候
  • 内省

显然,应该优先采用内省来监控。但是如果应用程序由第三方提供,并且你没有深入了解其内部操作的时候。从外
部查看应用程序以了解某些网络、安全性或可用性问题通常也很有帮助。

指标是一个组件属性的度量,对这个指标持续跟踪,观察的集合称为时间序列。

单一指标和聚合指标的组合可以提供最佳的健康视图:前者可深入到某个特定问题,而后者可以查看更高阶的状态。

使用平均值描述事件序列是非常危险的。有个笑话是:一位统计学家跳进平均深度只有 25 厘米的湖中,然后差点被
淹死,因为湖中有深达十米的大洞,虽然大部分水面深度只有 10cm

平局值的坏处就在于高峰和低谷可能被平均值所掩盖。百分位数才是识别异常值的理想选择。

监控方法论

Gregg 的 USE 指标:

  • Utilization(使用率). 资源忙于工作的平均时间,通常用随时间变化的百分比表示。
  • Saturatioin(饱和度). 资源排队工作的指标,无法处理额外的工作。通常用队列长度表示。
  • Error(错误). 错误事件的计数

Google 的四个黄金指标

  • 延迟:服务请求所花费的时间,需要区分成功和失败请求。因为失败请求可能延迟非常低,但是结果是错的
  • 流量:QPS 或者 TPS
  • 错误:错误的速率。包括空响应和超时等隐式错误
  • 饱和度:受限的资源,和上面类似

Weaveworks 的 RED 指标

  • Rate(流量)
  • Error(错误率)
  • Duration(延迟)

实际上已经被包含在 Google 的四个指标中了

警报和通知

  • 哪些问题需要通知
  • 谁需要被告知
  • 如何告知他们
  • 多久告知他们一次
  • 何时停止通知或升级到其他人

通知的信息应该包含以下几方面:

  • 清晰准确,可操作。应该让人能够看懂并知道如何操作
  • 为通知添加上下文,应该包含其他组件的相关信息
  • 只发送有意义的通知,不要因为有了通知系统就一定要使用

可视化

可视化系统最终要的在于:突出重点而不仅是提升视觉效果。

第二章

大多数监控查询和警报都是从最近(通常是一天内)的数据产生的。

Prometheus 的高可用架构建议:

  • 使用两个或多个相同配置的 Prometheus 服务器收集指标
  • 所有生成的警报发送到一个 Alertmanger 的集群,由 altermanager 进行消重

第三章

强烈建议不要单独配置每个服务的指标抓取间隔,这样能够确保你的所有时间序列具有相同的粒度。

即使向量 (Instant Vector): 一组包含每个时间序列的单个样本的时间序列集合

第四章

cAdvisor 作为容器运行,可以用来监控 Docker

如何设定标签体系?

  1. 使用拓扑标签,比如说 datacenter, job, instance 等
  2. 使用模式标签,比如 url_path, error_code

在这里书中有一处错误,书中说可以使用 user 作为标签,实际上绝对不要用 user 作为标签,这会让时间序列的 rank 直接爆炸

TODO avg(rate()) 是啥意思

predict_linear 这个函数非常有用,可以用来回答:”考虑到现在磁盘使用情况,以及他的增长速率,我们会在什么时候耗尽磁盘空间?

对于向量匹配运算,在多数情况下,一对一匹配就足够了。

第六章

最常见的错误是发送过多的警报。

应该针对症状而不是原因发出警报,又人类来判断造成问题的具体原因。

Alertmanger 的 route 块配置警报如何处理。receivers 配置警报的目的地。在规则中 expr 配置触发警报的规
则,而 for 指定在触发警报之前,测试表达式必须为 true 的时长,annotations 用于指定展示更多信息的标签。

警报一共有三个状态:

  • Inactive
  • Pending, 已经为 true, 但是 for 的时间还没满足
  • Firing, 处于触发状态

如果不指定 for 子句,那么警报会直接由 Inactive 转为 Firing.

在 annotation 中还可以使用模板,其中有一些变量,和 humanize 等等函数。

routes 是树形的,在 Yaml 配置中直接嵌套。

routes:
- match:
    severity: critical
  receiver: pager
  routes:
    - match:
        servrity: application
      receiver: support_team

Alert 的 silence 也很重要,可以通过

  • web 控制台
  • amtool
  • unsee 等第三方控制台

第七章

Prometheus 认为实现集群所投入的成本要高于数据本身的价值,所以 Prometheus 不用集群,直接两个配置走起。

第八章

为应用程序添加监控,从以下入口和出口做起:

  1. 测量请求和响应的数量和时间
  2. 测量对外部服务和 API 的调用次数和时间,比如对于数据库等的调用
  3. 测量作业调度,执行和其他周期性事件
  4. 测量重要业务和功能性事件的数量和时间

应用程序的指标又分为两大类

  1. 技术性指标,用于衡量应用程序的性能和状态,比如吞吐量、功能和状态等等
  2. 业务性指标,用于衡量应用程序的价值,比如电商网站的销售量

业务指标通常是应用程序指标的更进一步,他们通常与技术指标同义。一个技术性指标可能是交易服务的延迟,
而业务性指标可能是每个交易的价值

另外,对于长期业务指标来说,不要用监控系统了,一般来说还是用基于事件的统计系统。

第九章

mtail 用于从日志中抽取并发送时间序列

第十章

探针监控也称为黑盒监控

第十一章

当目标端点没有可以抓取的端点,比如批处理作业,可以使用 push gateway

Push Gateway 开箱即用,没有什么配置。

Web Cron 市场调研

因为实现了 sche 的语法,感觉完爆 cron,所以调研了一下 web cron,不过似乎市场不大。这个算是 unboundlling AWS 的一种,但是功能太简单了,直接用 AWS 也不复杂。而且竞争对手太多了。。

Heroku 真的是个很有意思的服务,解决了很多痛点,但是项目还是挺大的。不过值得思考的是,为什么 Heroku 做起来了,而 Google App Engine 凉了?是因为 GAE 超前时代太多了吗?

Sche.io 还没有被注册,可惜 99 刀太贵了,不然先注册一把。

  1. Indie Hacker 上有人有类似的想法。https://www.indiehackers.com/forum/cloud-based-cron-job-should-i-built-it-c46f58e66f
  2. Hacker News 上的评论大多是正面的,但是也不知道会不会付费。https://news.ycombinator.com/item?id=17346616
  3. 直接用 aws lambda 就行了。。https://gist.github.com/milesw/83332215df29fa25239712cd1ba273d9
  4. 这个 Cronhub 号称挣钱了。但是没看出哪里有意思或者可复制来。https://www.indiehackers.com/@tigran/cronhub-2nd-month-report-9e474add23

提到的一些痛点:

  1. 如何做好授权?
  2. 不一定所有服务都暴露了外部 http 接口
  3. 对于长时间执行的任务怎么办
  4. 能否直接 ssh 上去执行任务呢?
  5. 能否提供 API 让用户通过代码上传任务

如果真要做的话,应该把监控、日志统计等都做了。做到比 AWS、Azure、Heroku 的调度功能更加方便。

比较有意思的思考

网站的监控是一个挺大的领域

  1. 包括网站的功能监控,比如 Pingdom,HyperPing,PagerDuty,DataDog 等等
  2. 网站的用户监控,Google Analytics 和一系列的工具

关于 Heroku vs App Engine

这篇文章说的不错。Google 的第一个问题在于没有把用户放在心里,而是在 Demo 甚至 Show off 自己的 Infra。比如说:

  • 在 GAE 上用户需要大幅度修改自己的代码才能运行
  • 没有 SQL,只能用 Google 自己的 BigTable
  • 不支持最最最流行的 PHP

等等。。

而在 Heroku 上,用户可以随意使用已经很熟悉的 Postgres/Mongo/Redis/MySQL 等等数据库,也就是说迁移到 Heroku 几乎不费任何代价,而且还不用关心负载平衡、监控等运维细节。

另一方面,Heroku 最开始支持的是 Ruby on Rails,而这个社区在当时是非常活跃,而且乐于传教的。

最后一点,Google App Engine 甚至比 Heroku 贵不少。。说好的大厂不差钱呢?

sche – 一种人类能够看懂的 cron 语法

在 Linux 系统上,我们一般使用 cron 来设置定时任务,然而 cron 的语法还是有些佶屈聱牙的,几乎每次要修改的时候都需要查一下文档才知道什么意思,以至于有 crontab.guru 这种网站专门来解释 cron 的语法。

想象一下,能不能有一种让人一眼就能看懂的语法来表达周期性的调度操作呢?比如说这样:

every 10 minutes         , curl apple.com
every hour               , echo 'time to take some coffee'
every day at 10:30       , eat
every 5 to 10 minutes    , firefox http://news.ycombinator.com
every monday             , say 'Good week'
every wednesday at 13:15 , rm -rf /
every minute at :17      , ping apple.com
every 90 minutes         , echo 'time to stand up'

这样的配置文件是不是很容易懂呢?如果要写成 crontab 的格式大概是这样的:

*/10 * * * *    curl apple.com
0 * * * *       echo 'time to take some coffee'
30 10 * * *     eat
*/7 * * * *     firefox http://news.ycombinator.com  # 实际上是不对的,因为 cron 没法随机
0 0 * * MON     say 'Good week'
15 13 * * WED   rm -rf /
# every minute at :17  无法实现,因为 cron 中没有秒
0 0-21/3 * * *  echo 'time to stand up'  # 需要两条命令来完成每隔 90 分钟的操作
30 1-22/3 * * * echo 'time to stand up'

可以很明显看出,cron 的语法可读性还是差一些的,关键是维护起来更是像读天书一样。幸运的是,我在周末刚刚做了一个小工具,虽然还比较粗糙,但是也已经可以解析上面这种可读性比较好的语法。下面简单介绍一下如何使用:

介绍 sche

sche 是一个 Python 程序,所以可以使用 pip 直接安装:

pip install sche

安装之后,就会得到一个 sche 命令,帮助文件如下:

-> % sche -h
usage: sche [-h] [-f FILE] [-t]

A simple command like `cron`, but more human friendly.

The default configuration file is /etc/schetab, syntax goes like:

    # (optional) set time zone first
    timezone = +0800

    # line starts with # is a comment
    every 60 minutes, echo "wubba lubba dub dub"

    # backup database every day at midnight
    every day at 00:00, mysqldump -u backup

    # redirect logs so you can see them
    every minute, do_some_magic >> /some/output/file 2>&1

optional arguments:
  -h, --help            show this help message and exit
  -f FILE, --file FILE  configuration file to use
  -t, --test            test configuration and exit

我们只需要把需要执行的命令放到 /etc/schetab 文件下就好了,这里显然是在致敬 /etc/crontab。比如说:

-> % cat /etc/schetab
timzone = +0800
every 5 seconds, echo "wubba lubba dub dub"
every 10 seconds, date

-> % sche
wubba lubba dub dub
Tue Sep  1 22:15:01 CST 2020
wubba lubba dub dub
wubba lubba dub dub
Tue Sep  1 22:15:11 CST 2020
wubba lubba dub dub
wubba lubba dub dub
Tue Sep  1 22:15:21 CST 2020
wubba lubba dub dub

如何让 sche 像 cron 一样作为一个守护进程呢?秉承 Unix 一个命令只做一件事的哲学,sche 本身显然是不提供这个功能的,可以使用 systemd 实现,几行配置写个 unit 文件就搞定了。

sche 的来源

sche 是 schedule — 另一个 Python 库的一个 fork, schedule 支持这样的 Python 语句:

schedule.every(10).minutes.do(job)
schedule.every().hour.do(job)
schedule.every().day.at("10:30").do(job)
schedule.every().monday.do(job)
schedule.every().wednesday.at("13:15").do(job)
schedule.every().minute.at(":17").do(job)

然而我的需求是把时间配置独立出来,能够像 cron 一样存到一个文本文件里,而不是写成 Python 代码,于是提了一个 PR,增加了 when 这个方法来解析表达式。同时我还强烈需求时区支持,然而原版的 schedule 也不支持。所以就创建了一个 fork.

sche.when("every wednesday at 13:15").do(job)
sche.timezone("+0800").every().day.at("00:00").do(job)

最后,原生的 cron 命令实际上(至少我)已经极少用了,然而 crontab 的语法流传还是非常广的,在所有需要定时任务的地方,几乎都能看到 cron 的身影,比如说 Kubernetes job 等等,如果能够使用一种让正常人能随时看懂的语法,感觉还是很有意义的。

参考

  1. https://schedule.readthedocs.io/en/stable/
  2. https://crontab.guru/
  3. https://stackoverflow.com/q/247626/1061155

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/