学习Flask主站源码,构建自己的Web站点

大家好,我是肖恩,源码解析每周见

flask—website,是flask曾经的主站源码,使用flask制作,包含模版渲染,数据库操作,openID认证, 全文检索等功能。对于学习如何使用flask制作一个完备的web站点,很有参考价值,我们一起来学习它。

项目结构

flask-website已经归档封存,我们使用最后的版本8b08,包括如下几个模块:

模块 描述
run.py 启动脚本
websiteconfig.py 设置脚本
update-doc-searchindex.py 更新索引脚本
database.py 数据库模块
docs.py 索引文档模块
openid_auth.py oauth认证
search.py 搜素模块
utils.py 工具类
listings 一些展示栏
views 蓝图模块,包括社区,扩展,邮件列表,代码片段等
static 网站的静态资源
templates 网站的模版资源

flask-website的项目结构,可以作为flask的脚手架,按照这个目录规划构建自己的站点:

.
├── LICENSE
├── Makefile
├── README
├── flask_website
│   ├── __init__.py
│   ├── database.py
│   ├── docs.py
│   ├── flaskystyle.py
│   ├── listings
│   ├── openid_auth.py
│   ├── search.py
│   ├── static
│   ├── templates
│   ├── utils.py
│   └── views
├── requirements.txt
├── run.py
├── update-doc-searchindex.py
└── websiteconfig.py
  • run.py作为项目的启动入口
  • requirements.txt描述项目的依赖包
  • flask_website是项目的主模块,里面包括:存放静态资源的static目录; 存放模版文件的templates目录;存放一些蓝图模块的views模块,使用这些蓝图构建网站的不同页面。

网站入口

网站的入口run.py代码很简单,导入app并运行:

from flask_website import app
app.run(debug=True)

app是基于flask,使用websiteconfig中的配置进行初始化

app = Flask(__name__)
app.config.from_object('websiteconfig')

app中设置了一些全局实现,比如404页面定义,全局用户,关闭db连接,和模版时间:

@app.errorhandler(404)
def not_found(error):
    return render_template('404.html'), 404

@app.before_request
def load_current_user():
    g.user = User.query.filter_by(openid=session['openid']).first() 
        if 'openid' in session else None

@app.teardown_request
def remove_db_session(exception):
    db_session.remove()

@app.context_processor
def current_year():
    return {'current_year': datetime.utcnow().year}

加载view部分使用了两种方式,第一种是使用flask的add_url_rule函数,设置了文档的搜索实现,这些url执行docs模块:

app.add_url_rule('/docs/', endpoint='docs.index', build_only=True)
app.add_url_rule('/docs/<path:page>/', endpoint='docs.show',
                 build_only=True)
app.add_url_rule('/docs/<version>/.latex/Flask.pdf', endpoint='docs.pdf',
                 build_only=True)

第二种是使用flask的蓝图功能:

from flask_website.views import general
from flask_website.views import community
from flask_website.views import mailinglist
from flask_website.views import snippets
from flask_website.views import extensions
app.register_blueprint(general.mod)
app.register_blueprint(community.mod)
app.register_blueprint(mailinglist.mod)
app.register_blueprint(snippets.mod)
app.register_blueprint(extensions.mod)

最后app还定义了一些jinja模版的工具函数:

app.jinja_env.filters['datetimeformat'] = utils.format_datetime
app.jinja_env.filters['dateformat'] = utils.format_date
app.jinja_env.filters['timedeltaformat'] = utils.format_timedelta
app.jinja_env.filters['displayopenid'] = utils.display_openid

模版渲染

现在主流的站点都是采用前后端分离的结构,后端提供纯粹的API,前端使用vue等构建。这种结构对于构建小型站点,会比较复杂,有牛刀杀鸡的感觉。对个人开发者,还需要学习更多的前端知识。而使用后端的模版渲染方式构建页面,是比较传统的方式,对小型站点比较实用。

本项目就是使用模版构建,在general蓝图中:

mod = Blueprint('general', __name__)

@mod.route('/')
def index():
    if request_wants_json():
        return jsonify(releases=[r.to_json() for r in releases])

    return render_template(
        'general/index.html',
        latest_release=releases[-1],
        # pdf link does not redirect, needs version
        # docs version only includes major.minor
        docs_pdf_version='.'.join(releases[-1].version.split('.', 2)[:2])
    )

可以看到首页有2种输出方式,一种是json化的输出,另一种是html方式输出,我们重点看看第二种方式。函数render_template传递了模版路径,latest_release和docs_pdf_version两个变量值。

模版也是模块化的,一般是根据页面布局而来。比如分成左右两栏的结构,或者上下结构,布局定义的模版一般叫做layout。比如本项目的模版就从上至下定义成下面5块:

  • head 一般定义html页面标题(浏览器栏),css样式/js-script的按需加载等
  • body_title 定义页面的标题
  • message 定义一些统一的通知,提示类的展示空间
  • body 页面的正文部分
  • footer 统一的页脚

使用layout模版定义,将网站的展示风格统一下来,各个页面可以继承和扩展。下面是head块和message块的定义细节:

<!doctype html>
{% block head %}
<title>{% block title %}Welcome{% endblock %} | Flask (A Python Microframework)</title>
<meta charset=utf-8>
<link rel=stylesheet type=text/css href="{{ url_for('static', filename='style.css') }}">
<link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}">
<script type=text/javascript
  src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
{% endblock %}
<div class=box>
  ...
  <p class=nav>
    <a href="{{ url_for('general.index') }}">overview</a> //
    <a href="{{ url_for('docs.index') }}">docs</a> //
    <a href="{{ url_for('community.index') }}">community</a> //
    <a href="{{ url_for('extensions.index') }}">extensions</a> //
    <a href="https://psfmember.org/civicrm/contribute/transact?reset=1&id=20">donate</a>
  {% for message in get_flashed_messages() %}
    <p class=message>{{ message }}
  {% endfor %}
  ...

本项目首页的general/index继承自全局的layout,并对其中的body部分进行覆盖,使用自己的配置:

{% extends "layout.html" %}
    ....
{% block body %}
  <ul>
    <li><a href="{{ latest_release.detail_url }}">Download latest release</a> ({{ latest_release.version }})
    <li><a href="{{ url_for('docs.index') }}">Read the documentation</a>
    <li><a href="{{ url_for('mailinglist.index') }}">Join the mailinglist</a>
    <li><a href=https://github.com/pallets/flask>Fork it on github</a>
    <li><a href=https://github.com/pallets/flask/issues>Add issues and feature requests</a>
  </ul>
  ...
  • 这个列表主要使用了蓝图中传入的latest_release变量,展示最新文档(pdf)的url

数据库操作

网站有交互,必定要持久化数据。本项目使用的sqlite的数据库,比较轻量级。数据库使用sqlalchemy封装的ORM实现。下面的代码展示了如何创建一个评论:

@mod.route('/comments/<int:id>/', methods=['GET''POST'])
@requires_admin
def edit_comment(id):
    comment = Comment.query.get(id)
    snippet = comment.snippet
    form = dict(title=comment.title, text=comment.text)
    if request.method == 'POST':
        ...
        form['title'] = request.form['title']
        form['text'] = request.form['text']
        ..
        comment.title = form['title']
        comment.text = form['text']
        db_session.commit()
        flash(u'Comment was updated.')
        return redirect(snippet.url)
    ...
  • 创建comment对象
  • 从html的form表单中获取用户提交的title和text
  • 对comment对象进行赋值和提交
  • 刷新页面的提示信息(在模版的message部分展示)
  • 返回到新的url

借助sqlalchemy,数据模型的操作API简单易懂。要使用数据库,需要先创建数据库连接,构建模型等, 主要在database模块:

DATABASE_URI = 'sqlite:///' + os.path.join(_basedir, 'flask-website.db')
# 创建引擎
engine = create_engine(app.config['DATABASE_URI'],
                       convert_unicode=True,
                       **app.config['DATABASE_CONNECT_OPTIONS'])
# 创建session(连接)                
db_session = scoped_session(sessionmaker(autocommit=False,
                                         autoflush=False,
                                         bind=engine))
# 初始化
def init_db():
    Model.metadata.create_all(bind=engine)

# 定义基础模型
Model = declarative_base(name='Model')
Model.query = db_session.query_property()

Comment数据模型定义:

class Comment(Model):
    __tablename__ = 'comments'
    id = Column('comment_id', Integer, primary_key=True)
    snippet_id = Column(Integer, ForeignKey('snippets.snippet_id'))
    author_id = Column(Integer, ForeignKey('users.user_id'))
    title = Column(String(200))
    text = Column(String)
    pub_date = Column(DateTime)

    snippet = relation(Snippet, backref=backref('comments', lazy=True))
    author = relation(User, backref=backref('comments', lazy='dynamic'))

    def __init__(self, snippet, author, title, text):
        self.snippet = snippet
        self.author = author
        self.title = title
        self.text = text
        self.pub_date = datetime.utcnow()

    def to_json(self):
        return dict(author=self.author.to_json(),
                    title=self.title,
                    pub_date=http_date(self.pub_date),
                    text=unicode(self.rendered_text))

    @property
    def rendered_text(self):
        from flask_website.utils import format_creole
        return format_creole(self.text)

Comment模型按照结构化的方式定义了表名,6个字段,2个关联关系和json化和文本化的展示方法。

sqlalchemy的使用,在之前的文章中有过介绍,本文就不再赘述。

openID认证

一个小众的网站,构建自己的账号即麻烦也不安全,使用第三方的用户体系会比较合适。本项目使用的是Flask-OpenID这个库提供的optnID登录认证。

用户登录的时候,会根据用户选择的三方登录站点,跳转到对应的网站进行认证:

@mod.route('/login/', methods=['GET''POST'])
@oid.loginhandler
def login():
    ..
    openid = request.values.get('openid')
    if not openid:
        openid = COMMON_PROVIDERS.get(request.args.get('provider'))
    if openid:
        return oid.try_login(openid, ask_for=['fullname''nickname'])
    ..

从对应的模版上更容易理解这个过程, 可以看到默认支持AOL/Google/Yahoo三个账号体系认证:

{% block body %}
  <form action="" method=post>
    <p>
      For some of the features on this site (such as creating snippets
      or adding comments) you have to be signed in.  You don't need to
      create an account on this website, just sign in with an existing
      <a href=http://openid.net/>OpenID</a> account.
    <p>
      OpenID URL:
      <input type=text name=openid class=openid size=30>
      <input type=hidden name=next value="{{ next }}">
      <input type=submit value=Login>
    <p>
      Alternatively you can directly sign in by clicking on one of
      the providers here in case you don't know the identity URL:
    <ul>
      <li><a href=?provider=aol>AOL</a>
      <li><a href=?provider=google>Google</a>
      <li><a href=?provider=yahoo>Yahoo</a>
    </ul>
  </form>
{% endblock %}

在三方站点认证完成后,会建立本站点的用户和openid的绑定关系:

@mod.route('/first-login/', methods=['GET''POST'])
def first_login():
    ...
        db_session.add(User(request.form['name'], session['openid']))
        db_session.commit()
        flash(u'Successfully created profile and logged in')
    ...
  • session中的openid是第三方登录成功后写入session

三方登录的逻辑过程大概就如上所示,先去三方平台登录,然后和本地站点的账号进行关联。其具体的实现,主要依赖Flask-OpenID这个模块, 我们大概了解即可。

全文检索

全文检索对于一个站点非常重要,可以帮助用户在网站上快速找到适合的内容。本项目展示了使用whoosh这个纯python实现的全文检索工具,构建网站内容检索,和使用ElasticSearch这样大型的检索库不一样。总之,本项目使用的都是小型工具,纯python实现。

全文检索从/search/入口进入:

@mod.route('/search/')
def search():
    q = request.args.get('q') or ''
    page = request.args.get('page'type=int) or 1
    results = None
    if q:
        results = perform_search(q, page=page)
        if results is None:
            abort(404)
    return render_template('general/search.html', results=results, q=q)
  • q是搜素的关键字,page是翻页的页数
  • 使用perform_search方法对索引进行查询
  • 如果找不到内容展示404;如果找到内容,展示结果

在search模块中提供了search方法,前面调用的perform_search函数是其别名:

def search(query, page=1, per_page=20):
    with index.searcher() as s:
        qp = qparser.MultifieldParser(['title''content'], index.schema)
        q = qp.parse(unicode(query))
        try:
            result_page = s.search_page(q, page, pagelen=per_page)
        except ValueError:
            if page == 1:
                return SearchResultPage(None, page)
            return None
        results = result_page.results
        results.highlighter.fragmenter.maxchars = 512
        results.highlighter.fragmenter.surround = 40
        results.highlighter.formatter = highlight.HtmlFormatter('em',
            classname='search-match', termclass='search-term',
            between=u'<span class=ellipsis> … </span>')
        return SearchResultPage(result_page, page)
  • 从ttile和content中搜素关键字q
  • 设置使用unicode编码
  • 将检索结果封装成SearchResultPage

重点在index.searcher()这个索引, 它使用下面方法构建:

from whoosh import highlight, analysis, qparser
from whoosh.support.charset import accent_map
...
def open_index():
    from whoosh import index, fields as f
    if os.path.isdir(app.config['WHOOSH_INDEX']):
        return index.open_dir(app.config['WHOOSH_INDEX'])
    os.mkdir(app.config['WHOOSH_INDEX'])
    analyzer = analysis.StemmingAnalyzer() | analysis.CharsetFilter(accent_map)
    schema = f.Schema(
        url=f.ID(stored=True, unique=True),
        id=f.ID(stored=True),
        title=f.TEXT(stored=True, field_boost=2.0, analyzer=analyzer),
        type=f.ID(stored=True),
        keywords=f.KEYWORD(commas=True),
        content=f.TEXT(analyzer=analyzer)
    )
    return index.create_in(app.config['WHOOSH_INDEX'], schema)

index = open_index()
  • whoosh创建本地的索引文件
  • whoosh构建搜素的数据结构,包括url,title,,关键字和内容
  • 关键字和内容参与检索

索引需要构建和刷新:

def update_documentation_index():
    from flask_website.docs import DocumentationPage
    writer = index.writer()
    for page in DocumentationPage.iter_pages():
        page.remove_from_search_index(writer)
        page.add_to_search_index(writer)
    writer.commit()

文档索引构建在docs模块中:

DOCUMENTATION_PATH = os.path.join(_basedir, '../flask/docs/_build/dirhtml')
WHOOSH_INDEX = os.path.join(_basedir, 'flask-website.whoosh')

class DocumentationPage(Indexable):
    search_document_kind = 'documentation'

    def __init__(self, slug):
        self.slug = slug
        fn = os.path.join(app.config['DOCUMENTATION_PATH'],
                          slug, 'index.html')
        with open(fn) as f:
            contents = f.read().decode('utf-8')
            title, text = _doc_body_re.search(contents).groups()
        self.title = Markup(title).striptags().split(u'—')[0].strip()
        self.text = Markup(text).striptags().strip().replace(u'¶', u'')
    
    @classmethod
    def iter_pages(cls):
        base_folder = os.path.abspath(app.config['DOCUMENTATION_PATH'])
        for dirpath, dirnames, filenames in os.walk(base_folder):
            if 'index.html' in filenames:
                slug = dirpath[len(base_folder) + 1:]
                # skip the index page.  useless
                if slug:
                    yield DocumentationPage(slug)

  • 文档读取DOCUMENTATION_PATH目录下的源文件(项目文档)
  • 读取文件的标题和文本,构建索引文件

小结

本文我们走马观花的查看了flask-view这个flask曾经的主站。虽然没有深入太多细节,但是我们知道了模版渲染,数据库操作,OpenID认证和全文检索四个功能的实现方式,建立了相关技术的索引。如果我们需要构建自己的小型web项目,比如博客,完全可以以这个项目为基础,修改实现。

经过数周的调整,接下我们开始进入python影响力巨大的项目之一: Django。敬请期待。

小技巧

本项目提供了2个非常实用的小技巧。第1个是json化和html化输出,这样用户可以自由选择输出方式,同时站点也可以构建纯API的接口。这个功能是使用下面的request_wants_json函数提供:

def request_wants_json():
    # we only accept json if the quality of json is greater than the
    # quality of text/html because text/html is preferred to support
    # browsers that accept on */*
    best = request.accept_mimetypes 
        .best_match(['application/json''text/html'])
    return best == 'application/json' and 
       request.accept_mimetypes[best] > request.accept_mimetypes['text/html']

request_wants_json函数中判断头部的mime类型,进行根据是application/json还是text/html决定展示方式。

第2个小技巧是认证装饰器, 前面一个是登录验证,后一个是超级管理认证:

def requires_login(f):
    @wraps(f)
    def decorated_function(*args, **kwargs):
        if g.user is None:
            flash(u'You need to be signed in for this page.')
            return redirect(url_for('general.login', next=request.path))
        return f(*args, **kwargs)
    return decorated_function

def requires_admin(f):
    @wraps(f)
    def decorated_function(*args, **kwargs):
        if not g.user.is_admin:
            abort(401)
        return f(*args, **kwargs)
    return requires_login(decorated_function)

这两个装饰器,在view的API上使用, 比如编辑snippet需要登录,评论需要管理员权限:

@mod.route('/edit/<int:id>/', methods=['GET''POST'])
@requires_login
def edit(id):
    ...

@mod.route('/comments/<int:id>/', methods=['GET''POST'])
@requires_admin
def edit_comment(id):
    ...

参考链接

  • https://github.com/pallets/flask-website
学习Flask主站源码,构建自己的Web站点
转自:

困难的老王拉了困难的同行一把

前天晚上,一家北方房企的董事长悄悄抵京,面见了王健林。最近这位老板在到处借钱,只要本周能到账的,什么条件都可以谈。

饭桌上,老板对王健林说想借七个小目标,愿意把已经抵押出去的几个商业项目的十年经营权益交给万达。

老王很义气,大手一挥同意了,命令万达这周必须付款。老王也不容易,到去年年中,万达商管的有息负债仍然高达1500多亿。手下很担心项目的风控,老王生气了:

养你们就是解决这些问题的,不然要你们做啥用。

进京求援之前,这家北方房企经历了频繁的动荡,走完大规模裁员和合并组织架构的既定程序后,他们终于到了殊死一搏的时刻。

这个夜晚,千里之外的广东,粤系房企龙光在深交所主动描述了自己的财务近状。从二月下旬开始,去年情况还不错的龙光,几只债券开始放量暴跌,现在已经到了违约债价格。

龙光承认,三月份他们需要偿还的债券有53亿,但至今只兑付了:

11.3亿。

根据某机构的排名,前两个月,龙光的销售额虽然下降了50%,但仍然达到了132亿元。这个数据还算不错,但龙光怎么就到了这个地步,很多人想不通。

昨天,中海宏洋宣布用十个亿买下龙光在汕头景耀项目的全部股权。这个项目是两年前龙光用近15亿拿下的,现在的收购价则有大半是抵扣的项目债务,龙光真正能拿到的钱还不到五千万。

看看这个项目的二股东,大概就明白了。一些不知来历的公司和个人,竟然占据了这个项目近半的股权。

也是这个不平静的夜晚,闽系房企禹洲正式违约了。他们2023年到期的一只优先票据应该在2月份支付两千多万美元的利息,经过三十天宽限期后,禹洲没能凑到这笔钱。

禹洲真的不算躺平。今年,禹洲一共有大约6亿美元的债券到期。根据克尔瑞数据,禹洲前两个月的销售额仅为78亿,同比下降了四成。

今年1月份,禹洲决定把旗下物业公司的所有股权卖给华润。这个月初,禹洲又公告称,完成了旧债券本金总额95%的旧债券展期。

禹洲对票据持有人的支持表示了感谢,并恳请少数投资人不要采取任何破坏公司稳定性的激进手段。仅仅几天后,他们就倒在了剩下的那:

5%身上。

公开暴雷的第二天,禹洲继续公告说出售物业的交易,终于和华润签订了正式协议。根据规定,只要通过相关程序和批准,双方正式协议生效三天内,华润就得向禹洲支付三分之二的交易款,也就是近七个亿的现金。

命运和禹洲开了个天大的玩笑,救命稻草居然就晚来了一天。

能迅速变现的东西越来越少,在信用市场近乎崩溃的前提下,只剩下销售回款这条路。但大家正在面对的情况是:

前两个月的销售规模大跌;三月是今年还款压力最大的月份。

据统计,今年3-6月,170家房企共有162亿美元存量债和781的人民币债券到期。而在所有的到期债券中,有近三分之一都集中在三月。

在过去大半年时间消耗完几乎所有潜力后,决定房企们生死的,其实已经是非常微小的变量:

可能是一天,也可能是5%。

转自:https://mp.weixin.qq.com/s/EZL1OP8EhMsaKj_N-iFZ1g

制裁升级!拜登会不会拖世界下水?

作者 | 万连山

数据支持 | 勾股大数据(www.gogudata.com)

制裁升级!拜登会不会拖世界下水?

在全球能源短缺问题继续升温之际,美国继续推动“全面封杀”俄罗斯能源。

昨夜,拜登在新闻发布会上说,“今天,我宣布美国瞄准俄罗斯经济的大动脉,禁止所有俄罗斯石油、天然气和能源的进口。这意味着美国港口将不再接受俄罗斯石油,美国人民将对普京的战争机器造成又一次强有力的打击。”

英国紧随其后,商业与能源大臣称将“在2022年底前逐步停止进口俄罗斯石油和石油产品。”

消息出来,国际油价一度飙涨至139美元/桶, 美股也在震荡中收低。

制裁升级!拜登会不会拖世界下水?

看到这里,我脑海中突然涌现出一幅画面:

一头灰熊凶威凛凛,手持猎枪、担忧被袭击的平民,联邦警察,动物保护主义者,环保主义者,他们在森林里相遇。

这尴尬的局面如何打破?

有人忽然灵光一闪,大喝一声:灰熊通俄……

拜登再次升级对俄制裁,全面封杀俄罗斯石油和天然气,会产生那些后果?点击视频了解等多内容制裁升级!拜登会不会拖世界下水?👇

01

分歧益深

此前,虽然北约对俄罗斯展开了一系列制裁,但始终没有波及到石油与天然气。(尽管早在上个月,加拿大就宣布禁止进口俄油,但这只是象征性的,毕竟该国自2019年起就从没有进口过石油。)

随着能源价格飙升,英美与其他欧洲盟友,在是否禁止俄能源问题上的分歧,越来越明显。

很显然,后者对俄罗斯能源更加依赖。

从去年的数据看,美国市场大部分进口能源都来自加拿大,从俄罗斯购买的原油仅占3%。

同时,美国本身就是油气大国,页岩油产能正在逐步恢复中,当然乐于见到国际油气价格飙升。

再加上前段时间以每天7亿美元的资金,疯狂收购俄罗斯石油,美国现在的石油储备非常充足。

也正因为如此,在周二的民意调查中,超过70%美国人支持对俄罗斯石油的禁令。

制裁升级!拜登会不会拖世界下水?

当然,这并非没有代价。

美国国内普通汽油均价达到4.17美元/加仑,为近14年最高水平,而且还有上涨趋势。

而交通运输成本上升,势必会抬高终端商品价格,最终推高整体通胀率。美国1月的通胀已经高达7.5%,再往上升,会不会无法控制?

不会。这是一盘很大的棋,第三段会详细说明。

但在欧洲,实施这一禁令,要困难得多。

欧洲国家的工业、发电和取暖等,都需要大量的天然气,约40%依靠俄罗斯进口。随着能源价格的不断飙升,由此引发的通胀危机给经济发展和民生,已经造成严重的影响。

尤其是德国,55%的天然气供应依赖俄罗斯。如果效仿英美施行禁令,供电、运输、供暖和工业运转,都将陷入停滞。

其他欧盟国家也好不到哪去。

德国、法国、荷兰相继表态,就俄罗斯能源禁令持谨慎态度,不愿意与美国共进退。

按照欧洲媒体的说法:欧盟对俄制裁就是一种自残式行动。

目前,欧洲天然气期货价格,已经是美国的20多倍。与去年同期300欧元/千立方米的天然气价格相比,目前的价格已经飙升至3500欧元。

全球大宗商品暴涨的直接后果,是造成紧密相关国家通货膨胀。而由乌克兰往西,首当其冲的就是众多欧洲内陆小国。

国际能源价格如果继续飙升,最先倒下的绝不是俄罗斯,而是欧盟中经济比较落后的成员国。

而美国军火巨头的武器和能源巨头的页岩气不但不愁没人要,还能哄抬物价大发灾难财。欧洲却要为此付出惨重的代价——成为这波对俄制裁的最大受害者。

制裁升级!拜登会不会拖世界下水?

美国鼓动欧盟与俄罗斯作对可以,但这存在上限,不能撕破最后的脸皮,否则自己承受不起。

虽然华盛顿一直奏乐,欧盟就得一直舞,但总有累的时候。无法解决后顾之忧,就彻底为其卖命,基本是不可能的。

美、英、加不顾盟友死活,坚持扩大对俄制裁以推高国际油价。如果美欧在这一问题上互不让步,双方的内讧大戏或将上演。

制裁升级!拜登会不会拖世界下水?

这个场景可能再现。

02

谁能横刀立马

俄罗斯是全球最大的原油出口国,每天的供应量约为700万桶,占全球7%。

假如,欧美真的全面封禁俄罗斯油气,这么大的缺口,谁能补上?

尤其是,缺席国际市场多年的伊朗石油,回归之日至今不明

3月5日,俄外长要求美国保证制裁不会损害俄罗斯与伊朗之间的贸易。言下之意,西方对俄制裁,已成为伊核协议的绊脚石。

OPEC组织秘书长巴尔金都也在7日发出警告,欧佩克国家无法控制全球油价上涨,地缘政治才是决定者。

据《国会山报》消息,随着俄乌冲突持续,阿联酋和沙特领导人均拒绝接听拜登的电话,因为担心谈话内容会涉及中东产油国进出口问题。

同时,欧美在重启伊核谈判时,也没有足够考虑到中东国家的意愿。

制裁升级!拜登会不会拖世界下水?

全球第四大石油生产国、美国最大的海外原油供应国加拿大,虽然日产量超过500万桶,但该国向南的输油管道容量有限,只有一条通往其海岸线的原油管道。

目前加拿大西海岸正在建设一个大型液化天然气设施,但要到2025年前后才能完工。

加拿大自然资源部长威尔金森也声明:未来几个月,加拿大更有可能做的是帮助其他国家制定长期能源战略,而不是直接影响油价。

也就是说,目前全球没有任何产能,可以替代俄罗斯每天700万桶的石油出口。美国或许可以,但它当然不会这么做。

俄前总统梅德韦杰夫曾在7日表态:我只能祝贺“有远见”的欧洲同事,他们可靠地保护自己不受“阴险的”俄罗斯和北溪2号项目影响。

早在德国叫停北溪2号时,他就预测欧洲人很快将迎来“为1000立方米天然气支付2000欧元的美丽新世界”

现在来看,这不仅不是耸人听闻,反而还低估了现实。

据荷兰TTF指数(欧洲天然气主要交易指标),4月交割期货周一开盘价为2366.8美元/千立方米,90分钟内上涨至3888.4美元,比周五的结算价格2170.2美元,上涨了79%。

3月7日,塞尔维亚总统武契奇也做出提醒,如果俄罗斯石油被禁止,世界油价将比历史高点还要高出2-3倍。

若此话成真,届时所有原材料的价格都将飙到天上,我们所熟知的一切规则,可能都需要重塑。

天然气、石油等能源价格只是一个侧面,小麦、玉米等基础粮食价格的暴涨,才是最要命的,必然会传导至几乎所有生活必需品售价上。

制裁升级!拜登会不会拖世界下水?

而这些,都是现在的欧洲,正在经历的过程——极有可能面临一场崩溃式的通胀,最终形成政府巨大的财政赤字,摧毁数十年来欧洲人不劳而获的福利制度。

这股旋涡,正迅速向全世界发散。

03

拜登的“伟大”资产

美国的疫情治理与经济恢复,还不知道要花多少钱。可以说,建国200多年以来,美利坚几乎没有遭受过如此重大的危机。

太阳底下没有新鲜事。

地主家的房子漏了,自然不会亲自动手去补。

不知道还有没有人记得,2月20日,拜登在白宫记者会上说道:“通胀是一笔伟大的资产。”

语出四座皆惊。大王这是何意?

事出反常必有妖孽。

美国1月的通胀上升到40年新高7.5%,已经是很惊人的数字。但在其他国家,承受的压力只会更大。

列一组很直观的数据。

2014年,也就是上一次国际油价上100美元时,1欧元能换1.4美元。

现在呢?1欧元只能换1.1美元。

换句话说,在以美元结算的石油体系中,欧元不到十年贬值了26%。

同样的道理,英镑贬值22%,日元也贬值了15%。

制裁升级!拜登会不会拖世界下水?

如果把美国比作一只蚁后,近几年印出的海量美钞,就是刚孵化出的工蚁。

在和平时期,蚁后乐善好施,不断派遣幼工蚁帮助邻居建设发展,参与到经济和商业流通中,渗透进世界每一个角落。

当这些工蚁成年,蚁后会大幅、高频、快速加息,召唤孩儿们回归母巢,也就是美国本土。

比如在70年代,美国经济被越南战争拖垮,通胀率最高达到13.3%。危急时刻,尼克松放出史上最强力财长——沃尔克。此君一上任,就把美元基准利率升到22.4%。

这一夸张的举动就像咒语,流落在全球的美元纷纷回归本土,帮助美国经济从最艰难的时期一跃而起。

而那些借了美债的国家,经济一下倒退了十年。

制裁升级!拜登会不会拖世界下水?

最近一系列事件的进展,与当年何其相似?

而接下来两个月,美国通胀水平极有可能在8%以上。不论到什么水平,通过即将到来的美联储加息,同时释放战略石油储备,压制本国油价,美国通胀必然会有一定程度回落。

其他国家,如果不付诸行动,飙升的油价,将使得通胀全线失控。这也是为什么,从去年年底开始,加拿大和英国突然加息,走在美国前面。毕竟谁也不傻。

届时,所有海外的资金,为了避免大幅贬值,都会流向美国本土。等于是利用全球的钱,让本国经济从废墟中站起来,从而实现“再次伟大”。

以这个角度来看,通胀对美国而言确实是一笔“伟大”的财富。

但这一次,还能如愿以偿吗?

最近三十年,美利坚开支无度、穷兵黩武,赤字连创新高,动不动就对敌国制裁,世界早就开始“去美元化”进程,美元占全球央行外汇储备的比例,从71%下滑到了59%。

再加上前不久,俄罗斯被踢出swift系统,央行4000亿美元的储备、俄富豪的大量美元资产也被冻结。

这让所有富人都意识到,私人财产不可侵犯这句话有多么可笑。以李嘉诚甩卖英国千亿资产为例,大量资金会撤出,寻找美元以外的替代品。

制裁升级!拜登会不会拖世界下水?

替代品有哪些?除欧元、日元、英镑,人民币也是一个越来越有力的竞争者。

美国加息,中国降准,可以看到,双方的货币政策正好相反。如果能守住,就能争取到更多惊慌失措的资金。

就像在这一轮俄乌危机中,大量欧洲资金进入中国避险,提升了中国资产吸引力一样。

这对我们的市场,是很大的利好。假以时日,究竟是谁实现“再次伟大”,还说不准。

04

尾声

三十年来,这个世界处于单极,或者说垄断状态。

身处糟糕的世界,特别是有一个自私的老大,谁也无法独善其身。

今天A股,又遭一记闷棍,比昨天还痛。

两市上涨1077只,下跌3525只,跌停28家。沪指一度失守3200点,创业板2600也没了。

从高开低走,午后跳崖,到尾盘深V,多少韭菜无语凝噎。

不光是大A,现在全球市场都是软妹子,动不动就大跌。

再这样下去,迟早要出事。

制裁升级!拜登会不会拖世界下水?

好消息是,乌克兰总统表示,对加入北约已经心灰意冷。俄乌战争的转机是否将要到来?股市是否能被拯救?全得看这位老哥何时会认怂。

最后,希望亏麻了的朋友,不要心灰意冷。给你们推荐一本书,相信会有些帮助。

制裁升级!拜登会不会拖世界下水?

为什么书皮是绿色的?

健康。

转自:https://mp.weixin.qq.com/s/LhoOksOBys_fRRdPfxYR4g

诗词书单收集

利用故事性强和趣味性浓的书籍中搭建诗词背后的历史框架。

推荐书单

♦️Top1:《鲜衣怒马少年时1、2》

♦️Top2:《枕上诗书——遇见最美唐诗》

《枕上诗书——遇见最美宋词》

他们有才,亦有爱——宋词中的缱绻爱情

♦️Top3:《唐诗背后那些有趣的灵魂》

《宋词背后那些有趣的灵魂》

——唐朝:从沙漠骆驼王昌龄

到京城四少王维,再从放荡不羁李白到朦胧诗派李商隐…

——宋朝:从歌女之友柳永到人性之光范仲淹

,再从生活虐我千百遍,我待生活如初恋的苏轼…

你们想了解这些诗词大咖们的爱恨情仇,这三套书统统给你安排的明明白白。

Apriori 关联规则算法(Python代码)

Apriori 关联规则算法(Python代码)

一、关联规则概述
1993年,Agrawal等人在首先提出关联规则概念,迄今已经差不多30年了,在各种算法层出不穷的今天,这算得上是老古董了,比很多人的年纪还大,往往是数据挖掘的入门算法,但深入研究的不多,尤其在风控领域,有着极其重要的应用潜力,是一个被低估的算法,很少见到公开的文章提及,我尝试一一剖析,希望给你带来一定的启示。
我倒是进行了比较深刻、全面的思考,并进行了大量的实验,这个话题感觉可以聊三天三夜。世界风云变幻,但本质没变化,各种关联一直存在,有意或无意的!
比如你女朋友,低头玩手指+沉默,那大概率生气了,那这就是你总结出来的规则。啤酒与尿布的例子相信很多人都听说过吧,故事是这样的:在一家超市中,人们发现了一个特别有趣的现象,尿布与啤酒这两种风马牛不相及的商品居然摆在一起,但这一奇怪的举措居然使尿布和啤酒的销量大幅增加了。为什么有这么奇怪现象呢?是因为美国妇女在丈夫回家前买尿布,然后丈夫顺手买了自己喜欢的啤酒,所以发生了这么有趣的事情。
很多人只记住了啤酒尿不湿,很少深入思考,我们稍微转换下,日常的事情,也存在非常多的关联规则?

二、应用场景举例

1、股票涨跌预测

放量+高换手率 -> 大概率上涨,历史数据挖掘,假如发现放量+高换手率的股票大概率上涨,则挖掘当天满足条件的个股,然后第二天买入,躺赚。

2、视频、音乐、图书等推荐

根据历史数据,如果大规模的存在某些用户看剧列表为:小时代 -> 上海堡垒,那么一个新的用户看了小时代,马上就给推荐上海堡垒,那大概率也会被观看,呼兰的账号,就是这么脏的。

3、打车路线预测(考虑时空)

根据大量的数据挖掘出以下规则
早上:起点家->目的地公司,
晚上:起点家->目的高铁站
周末:起点家->目的地购物中心
那当你每天早上打开软件的时候,打车软件就会推荐你的公司作为目的地,大大的减少用户的打车时间。如下图,我输入小区名称,马上给我推荐了三个地方,杭州东站第一位,因为平时的打车这个组合的支持度最高。
Apriori 关联规则算法(Python代码)

4、风控策略自动化挖掘

根据历史标题,总结出规律发现商品标题包含 老司机+百度网盘 -> 色情风险高,那后面遇到这标题包含这两个词语的,就直接拒绝了。
根据历史行为数据,发现了沉默用户+非常用地登录+修改密码->大概率都被盗号了,那一个新的账户满足这个三个条件,那马上就进行账户冻结或者实人认证,就能避免盗号风险的发生。
根据历史数据,发现用户A +B 每天都相隔10s登录 ,则可以认为A、B存在关联关系,可能是机器控制的同一批薅羊毛账户。
风控策略的自动化挖掘,这个也是我们后续要重点关注和讲解的地方。

三、3个最重要的概念

关联规则有三个核心概念需要理解:支持度、置信度、提升度,下面用最经典的啤酒-尿不湿案例给大家举例说明这三个概念,假如以下是几名客户购买订单的商品列表:
Apriori 关联规则算法(Python代码)

1、支持度

支持度 (Support):指某个商品组合出现的次数总订单数之间的比例。
在这个例子中,我们可以看到“牛奶”出现了 4 次,那么这 5 笔订单中“牛奶”的支持度就是 4/5=0.8。
Apriori 关联规则算法(Python代码)
同样“牛奶 + 面包”出现了 3 次,那么这 5 笔订单中“牛奶 + 面包”的支持度就是 3/5=0.6
Apriori 关联规则算法(Python代码)
这样理解起来是不是非常简单了呢,大家可以动动手计算下 ‘尿不湿+啤酒’的支持度是多少?

2、置信度

置信度 (Confidence):指的就是当你购买了商品 A,会有多大的概率购买商品 B,在包含A的子集中,B的支持度,也就是包含B的订单的比例。
置信度(牛奶→啤酒)= 3/4=0.75,代表购买了牛奶的订单中,还有多少订单购买了啤酒,如下面的表格所示。
Apriori 关联规则算法(Python代码)
置信度(啤酒→牛奶)= 3/4=0.75,代表如果你购买了啤酒,有多大的概率会购买牛奶?
Apriori 关联规则算法(Python代码)
置信度(啤酒→尿不湿)= 4/4=1.0,代表如果你购买了啤酒,有多大的概率会买尿不湿,下面的表格看出来是100%。
Apriori 关联规则算法(Python代码)
由上面的例子可以看出,置信度其实就是个条件概念,就是说在 A 发生的情况下,B 发生的概率是多大。如果仅仅知道这两个概念,很多情况下还是不够用,需要用到提升度的概念。比如A出现的情况下B出现的概率为80%,那到底AB是不是有关系呢,不一定,人家B本来在大盘中的比例95%。你的A出现,反而减少了B出现的概率。

3、提升度

提升度 (Lift):我们在做商品推荐或者风控策略的时候,重点考虑的是提升度,因为提升度代表的是A 的出现,对B的出现概率提升的程度。
提升度 (A→B) = 置信度 (A→B)/ 支持度 (B)
所以提升度有三种可能:
  • 提升度 (A→B)>1:代表有提升;

  • 提升度 (A→B)=1:代表有没有提升,也没有下降;

  • 提升度 (A→B)<1:代表有下降。

提升度 (啤酒→尿不湿) =置信度 (啤酒→尿不湿) /支持度 (尿不湿) = 1.0/0.8 = 1.25,可见啤酒对尿不湿是有提升的,提升度为1.25,大于1。
可以简单理解为:在全集的情况下,尿不湿的概率为80%,而在包含啤酒这个子集中,尿不湿的概率为100%,因此,子集的限定,提高了尿不湿的概率,啤酒的出现,提高了尿不湿的概率。

4、频繁项集

频繁项集(frequent itemset) :就是支持度大于等于最小支持度 (Min Support) 阈值的项集,所以小于最小值支持度的项目就是非频繁项集,而大于等于最小支持度的的项集就是频繁项集,项集可以是单个商品,也可以是组合。
频繁集挖掘面临的最大难题就是项集的组合爆炸,如下图:
Apriori 关联规则算法(Python代码)
随着商品数量增多,这个网络的规模将变得特别庞大,我们不可能根据传统方法进行统计和计算,为了解决这个问题,Apriori算法提出了两个核心思想:
某个项集是频繁的,那么它的所有子集也是频繁的
{Milk, Bread, Coke} 是频繁的 → {Milk, Coke} 是频繁的
如果一个项集是 非频繁项集,那么它的所有超集也是非频繁项集
{Battery} 是非频繁的 → {Milk, Battery} 也非平凡
如下图,如果我们已知B不频繁,那么可以说图中所有绿色的项集都不频繁,搜索时就要这些项避开,减少计算开销。
Apriori 关联规则算法(Python代码)
同理,如果下图所示,{A,B}这个项集是非频繁的,那虚线框后面的都不用计算了,运用Apriori算法的思想,我们就能去掉很多非频繁的项集,大大简化计算量,当然,面对大规模数据的时候,这种排除还是解决不了问题,于是还有FP-Growth(Frequent pattern Growth,频繁模式增长树)这种更高效的方法,后面有机会慢慢讲。
Apriori 关联规则算法(Python代码)
需要注意的是:
1)如果支持度和置信度阈值过高,虽然可以在一定程度上减少数据挖掘的时间,但是一些隐含在数据中的非频繁特征项容易被忽略掉,难以发现足够有用的规则;
2)如果支持度和置信度阈值过低,可能会导致大量冗余和无效的规则产生,导致较大计算量负荷。

四、Python算法介绍

这里用的是Python举例,用的包是apriori,当然R语言等其他语言,也有对应的算法包,原理都是一样的,大家自行进行试验。
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
#包安装 我们使用efficient-apriori,python中也可以利用apyori库和mlxtend库pip install efficient-apriori
#加载包from efficient_apriori import apriori
‘’‘apriori(transactions: typing.Iterable[typing.Union[set, tuple, list]], min_support: float=0.5, min_confidence: float=0.5, max_length: int=8, verbosity: int=0, output_transaction_ids: bool=False)上面就是这个函数的参数min_support:最小支持度min_confidence:最小置信度max_length:项集长度‘’‘
# 构造数据集data = [('牛奶','面包','尿不湿','啤酒','榴莲'), ('可乐','面包','尿不湿','啤酒','牛仔裤'), ('牛奶','尿不湿','啤酒','鸡蛋','咖啡'), ('面包','牛奶','尿不湿','啤酒','睡衣'), ('面包','牛奶','尿不湿','可乐','鸡翅')]#挖掘频繁项集和频繁规则itemsets, rules = apriori(data, min_support=0.6, min_confidence=1)#频繁项集print(itemsets){1: {('啤酒',): 4, ('尿不湿',): 5, ('牛奶',): 4, ('面包',): 4}, 2: {('啤酒''尿不湿'): 4, ('啤酒''牛奶'): 3, ('啤酒''面包'): 3, ('尿不湿''牛奶'): 4, ('尿不湿''面包'): 4, ('牛奶''面包'): 3}, 3: {('啤酒''尿不湿''牛奶'): 3, ('啤酒''尿不湿''面包'): 3, ('尿不湿''牛奶''面包'): 3}}itemsets[1] #满足条件的一元组合{('啤酒',): 4, ('尿不湿',): 5, ('牛奶',): 4, ('面包',): 4}itemsets[2]#满足条件的二元组合{('啤酒', '尿不湿'): 4,('啤酒', '牛奶'): 3,('啤酒', '面包'): 3,('尿不湿', '牛奶'): 4,('尿不湿', '面包'): 4,('牛奶', '面包'): 3}itemsets[3]#满足条件的三元组合{('啤酒', '尿不湿', '牛奶'): 3, ('啤酒', '尿不湿', '面包'): 3, ('尿不湿', '牛奶', '面包'): 3}#频繁规则print(rules)[{啤酒} -> {尿不湿}, {牛奶} -> {尿不湿}, {面包} -> {尿不湿}, {啤酒, 牛奶} -> {尿不湿}, {啤酒, 面包} -> {尿不湿}, {牛奶, 面包} -> {尿不湿}]
#我们把max_length=2这个参数加进去看看itemsets, rules = apriori(data, min_support=0.6,min_confidence=0.5,max_length=2)#频繁项集print(itemsets){1: {('牛奶',): 4, ('面包',): 4, ('尿不湿',): 5, ('啤酒',): 4, ('R',): 4}, 2: {('R', '啤酒'): 4, ('R', '尿不湿'): 4, ('R', '牛奶'): 3, ('R', '面包'): 3, ('啤酒', '尿不湿'): 4, ('啤酒', '牛奶'): 3, ('啤酒', '面包'): 3, ('尿不湿', '牛奶'): 4, ('尿不湿', '面包'): 4, ('牛奶', '面包'): 3}}#通过这个数据我们可以看到,项集的长度只包含有两个项了
五、挖掘实例
每个导演都有自己的偏好、比如周星驰有星女郎,张艺谋有谋女郎,且巩俐经常在张艺谋的电影里面出现,因此,每个导演对演员的选择都有一定的偏爱,我们以宁浩导演为例,分析下选择演员的一些偏好,没有找到公开的数据集,自己手动扒了一部分,大概如下,有些实在有点多,于是简化下进行分析。
Apriori 关联规则算法(Python代码)
可以看到,我们一共扒了9部电影,计算的时候,支持度的时候,总数就是9.
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
#把电影数据转换成列表 data = [['葛优','黄渤','范伟','邓超','沈腾','张占义','王宝强','徐峥','闫妮','马丽'], ['黄渤','张译','韩昊霖','杜江','葛优','刘昊然','宋佳','王千源','任素汐','吴京'], ['郭涛','刘桦','连晋','黄渤','徐峥','优恵','罗兰','王迅'], ['黄渤','舒淇','王宝强','张艺兴','于和伟','王迅','李勤勤','李又麟','宁浩','管虎','梁静','徐峥','陈德森','张磊'], ['黄渤','沈腾','汤姆·派福瑞','马修·莫里森','徐峥','于和伟','雷佳音','刘桦','邓飞','蔡明凯','王戈','凯特·纳尔逊','王砚伟','呲路'], ['徐峥','黄渤','余男','多布杰','王双宝','巴多','杨新鸣','郭虹','陶虹','黄精一','赵虎','王辉'], ['黄渤','戎祥','九孔','徐峥','王双宝','巴多','董立范','高捷','马少骅','王迅','刘刚','WorapojThuantanon','赵奔','李麒麟','姜志刚','王鹭','宁浩'], ['黄渤','徐峥','袁泉','周冬雨','陶慧','岳小军','沈腾','张俪','马苏','刘美含','王砚辉','焦俊艳','郭涛'], ['雷佳音','陶虹','程媛媛','山崎敬一','郭涛','范伟','孙淳','刘桦','黄渤','岳小军','傅亨','王文','杨新鸣']] #算法应用 itemsets, rules = apriori(data, min_support=0.5, min_confidence=1) print(itemsets){1: {('徐峥',): 7, ('黄渤',): 9}, 2: {('徐峥', '黄渤'): 7}} print(rules) [{徐峥} -> {黄渤}]
通过上述分析可以看出:
在宁浩的电影中,用的最多的是黄渤和徐峥,黄渤9次,支持度100%,徐峥7次,支持度78%,(‘徐峥’, ‘黄渤’) 同时出现7次,置信度为100%,看来有徐峥,必有黄渤,真是宁浩必请的黄金搭档,且是一对好基友。
 
当然,这个数据量比较小,我们基本上肉眼也能看出来,这里只是提供一个分析案例和基础方法,巩固下基础知识,算是开胃菜,大规模的数据,人眼无法直接感知的时候,算法的挖掘与发现,就显得特别有意义了,后续会陆续推出相应的文章。

 

转自:https://mp.weixin.qq.com/s/2iNO3som2MUnhJroMn9y5Q