[算法] 最短路径 (BellmanFord 算法)

import numpy as np

n, m = map(int, input().split(' '))

dis = np.zeros(10, dtype=np.int)
bak = np.zeros(10, dtype=np.int)

u = np.zeros(10, dtype=np.int)
v = np.zeros(10, dtype=np.int)
w = np.zeros(10, dtype=np.int)
inf = 99999999

for i in range(1, m+1):
    u[i], v[i], w[i] = map(int, input().split(' '))

for i in range(1, n+1):
    dis[i] = inf
dis[1] = 0

# Bellman-Ford Algorithm
for k in range(1, n):
    for i in range(1, n+1):
        bak[i] = dis[i]
    for i in range(1, m+1):
        if dis[v[i]] > dis[u[i]] + w[i] :
            dis[v[i]] = dis[u[i]] + w[i]
    check = 0
    for i in range(1, n+1):
        if bak[i] != dis[i]:
            check = 1
            break
    if check == 0:
        break

flag = 0
for i in range(1, m+1):
    if dis[v[i]] > dis[u[i]] + w[i]:
        flag = 1
if flag == 1 :
    print("此图含有负权回路")
else:
    for i in range(1, n+1):
        print(dis[i])

'''
5 5
2 3 2
1 2 -3
1 5 5
4 5 2
3 4 3
'''

Folium_将geojson以polygon创建包含popup的图层

上一篇谈到folium目前无法在创建geoJson时,同时包含popup,所以目前想到的方式是利用pandas读取json数据,再以polygon读取座标,并同时设置popup。
卡在上一篇的问题在于,polygon读取的座标顺序是[y, x],但是在geoJson里面却是以[x, y],所以要让polygon能顺利创建,需要再多做一步来转换:

newList = []
def switchLatLng(geos):
    for geo in geos:
        if isinstance(geo[0],list):
            switchLatLng(geo)
        else:
            newList.append([geo[1], geo[0]])
    return newList

这边利用迭代方式,让程序判断数据在剩下单一点位座标时,替换x, y座标。测试有成功将座标转换,现在正拿去跑folium中,已经过了半小时依旧没有动静。可能是因为有891个里的关系?由于folium制作leaflet的方式,是将所有东西全部写进去一个html里面,所以如果要画像是里界分布的地图,在那张地图的html文件里面,就会有高雄市891个里的所有座标点数据。所以电脑很容易就当机,像是第27天时产出的区界地图就有700多万个字符,可想而知里界的话会有多可怕,这也是folium目前的缺点之一。


一个半小时后……

最后发现是我在里面创建了newList,但是都没有清空,所以累积了891个里的座标点,跑到电脑内存都爆了当然跑出来也不会是我们要的结果。最后改完成功的代码,用python跑也不用1分钟……。

myMap = folium.Map([22.73444963475145, 120.28458595275877], zoom_start=6)
kaoVill = folium.FeatureGroup()
features = pd.read_json('../../dist/mapdata/KaoVillageRange.json')["features"]
for feature in features:
    villpopup = '这里是

'+feature['properties']['TV_ALL']+'

总面积为'+feature['properties']['AREA']+'平方公尺' coors = [switchLatLng(feature['geometry']['coordinates'])] newList = [] villRange = folium.Polygon(coors, popup = villpopup, fill = True).add_to(kaoVill) kaoVill.add_to(myMap) myMap.fit_bounds(kaoVill.get_bounds()) myMap.save('Folium_KaoVillpopup.html')

这里我创建一个FeatureGroup,让地图能够fitBounds,并在创建之后把newList清空,避免创建新的里界时也把之前的一起抓进来。
看一下成果吧!

folium_KaoVillRange

大功告成!!!
终于~~

Django部署应用程序

建立Heroku帐号

首先先去 https://heroku.com/ 办帐号,Heroku办帐号是免费的,只要点sign up 就可以办了

安装Heroku Toolbelt

要在Heroku伺服上部署和管理项目,我们需要用到Heroku Toolbelt中所提供的工具,我们需要在虚拟环境中输入pip install dj-database-urlpip install dj-staticpip install static3pip install gunicorn

一定要照顺序逐个执行这些指令,这样我们就会知道哪些套件未能安装,dj-database-url套件能协助Django与Heroku使用的资料库进行沟通

建立requirements.txt放入套件清单

Heroku需要了解我们项目是依赖哪些套件,因此我们会用pip来生成一个文件,在其中列出这些套件清单,首先启动虚拟环境后输入pip freeze > requirements.txt命令,freeze命令能让pip把项目中目前安装的所有套件名称都写入requirements.txt档中,接着我们开启这个txt文件看看,然后在里面新增下图红色框框的内容,新增的psycopg2能协助Heroku管理作用中的资料库,如下

指定python版本

如果没有指定python的版本,Heroku会使用目前python的预设版本,所以我们要先确定我们使用的python版本,我们要先在虚拟环境里输入python --version命令,我们就会看到它显示的python版本,我们在这是用python3.7.0的版本,接下来我们要在manage.py所在的资料夹中建立一个runtime.txt档,并输入以下内容来指定python版本

修改setting.py来部署到Heroku

我们要开启setting.py然后在最后面的位置加入一个部分来定义Heroku环境相关设定,如下

cwd = os.getcwd() # 取得工作目录
if cwd == '/app' or cwd[:4] == '/tmp':
    import dj_database_url # 汇入dj_database_url用在Heroku上来配置服务器
    DATABASES = {
        'default': dj_database_url.config(default='postgres://localhost')
    }
    
    # 支援HTTPS的请求
    SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
    
    # 确保Django会从Heroku的URL中尉项目提供服务
    ALLOWED_HOSTS = ['learning-log-final.herokuapp.com']
    DEBUG = False

    # 设定项目让它能在Heroku上正确提供静态档
    BASE_DIR = os.path.dirname(os.path.abspath(__file__))
    STATIC_ROOT = 'staticfiles'
    STATICFILES_DIRS = (
        os.path.join(BASE_DIR, 'static'),
    )

建立Procfile来启动处理程序

Procfile会告诉Heroku启动哪些处理程序来正确地位项目提供服务,建立一个Procfile(P一定要大写)文件,不用设副档名,存在和manage.py同一个资料夹中,文件内容如下

web: gunicorn learning_log.wsgi --log-file -

这行程序告诉Heroku使用gunicorn作为服务器,并使用learning_log资料夹中的wsgi.py中的设定来启动应用程序

修改Heroku的wsgi.py档

我们汇入了能帮助正确提供静态文件的Cling,并用来启动应用程序

import os

from django.core.wsgi import get_wsgi_application
from dj_static import Cling # 新增区块

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'learning_log.settings') # 新增区块
application = Cling(get_wsgi_application())

为静态档制作一个目录

在Heroku上Django会集合所有的静态档并放在一个地方,这样能提高管理和运用的效率,我们会建立一个存放静态文件的目录,在learning log/learning log/static的资料夹内,新增一个placeholder.txt,内容如下

This file ensures that learning_log/static/ will be added to the project. 
Django will collect static files and place them in learning_log/static/.

使用Git来追踪文件

Git是个版本控制程序,能让我们每次成功时做出新功能时撷取项目代码的快照来备存,不论出现甚么问题,都可以轻松还原到最新可用的那一个快照纪录,每个快照都称为commit

安装Git

Heroku Toolbelt含有Git在其中,因此它应该已经安装在系统内了,执行git --version命令,可以看到现在的版本

配置Git

Git可追踪是谁修改了项目,就算项目只有一个人开发也是这样的情况,若要开始记录追踪项目,Git会需要我们的名称和email
(learning_log) C:\Users\ASUS\learning_log>git config --global user.name "weiting"
(learning_log) C:\Users\ASUS\learning_log>git config --global user.email "b22379222@gmail.com"

提交项目

  1. 执行git init命令,在learning log项目所在的目录中初始化一个空的仓库
  2. 我们执行git add .命令,这会把未被忽略的文件新增到这个仓库中
  3. 执行git commit -am "Ready for deployment to heroku"命令,其中-a旗标是让Git在这个提交中放入所有修改过的文件,-m旗标会让Git记录一条log讯息
  4. 执行git status命令,输出指出目前位在master分支中,而工作目录是干净的

推送到Heroku

  1. 执行heroku login来登入Heroku
  2. 执行heroku create建立一个空项目
  3. 执行git push heroku master命令让Git把项目的master分支推送到Heroku刚才建立的仓库中,Heroku会使用这些文件在服务器上配置项目,然后列出用来存取这个项目的URL
  4. 执行heroku ps命令会看到项目处于作用中的状态
  5. 执行heroku open命令能免除开启浏览器并输入Heroku给定的URL

在Heroku上配置资料库

我们需要执行一次迁移来设定即时资料库,并套用我们在开发过程中生成的所有迁移,要对Heroku项目执行Django和Python的命令,可使用heroku run命令,执行heroku run python manage.py migrate命令,在这里Django套用预设的迁移和我们在开发learning log期间生成的迁移


好了~我们的Django实作部分如期的在十天内结束了,IT的30天也差不多到了结尾,明天会最后一篇会打这30天的心得感想

附上排版较精美的
HackMD网址:https://hackmd.io/MpDyV3uSSxu9bLGeeMeSRg

今天结束,各位明天见


资料来源:<>-Eric Matthes着/H&C译

[演算法] 最短路径 (Dijkstra 演算法)

import numpy as np

e = np.zeros((10, 10), dtype=int)
inf = 99999999
key_point = 1

n, m = map(int, input().split(' '))
for i in range(1, n+1):
    for j in range(1, n+1):
        if i == j : 
            e[i][j] = 0
        else:
            e[i][j] = 1

for i in range(1, m+1):
    t1, t2, t3 = map(int, input().split(' '))
    e[t1][t2] = t3

dis = np.zeros(10, dtype=int)
for i in range(1, n+1):
    dis[i] = e[key_point][i]

book = np.zeros(10, dtype=int)
for i in range(1, n+1):
    book[i] = 0
book[i] = 1

# Dijkstra Algorithm
for i in range(1, n):
    min = inf
    for j in range(1, n+1):
        if book[j] == 0 and dis[j] < min :
            min = dis[j]
            u = j

    for v in range(1, n+1):
        if e[u][v] < inf:
            if dis[v] > dis[u] + e[u][v]:
                dis[v] = dis[u] + e[u][v]

for i in range(1, n+1):
    print(dis[i])



'''
test data:
6 9
1 2 1
1 3 12
2 3 9
2 4 3
3 5 5
4 3 4
4 5 13
4 6 15
5 6 4
'''

Folium_显示图层资讯

在Leaflet及Folium均熟悉并尝试建立一些地图的实例时,会发现其实Folium在很多地方尚未成熟,像是在popup的部分,目前还无法在geoJSON上实现。

Leaflet

在Leaflet中,要使用popup就方便许多,因为他属于Layer的method,因此可轻易建立,这部分在第26天已经有提到:

fetch("/jsons/taiwanDistrictRange.json")
    .then(response => response.json())
    .then(json => {
    kaoDist = L.geoJSON(json).bindPopup(function (layer) {
        return layer.feature.properties.T_Name;
    }).addTo(map);
    map.fitBounds(kaoDist.getBounds());
});

Folium

那Folium目前的状况要如何解呢?
或许有其他更好的方法,但我现在想到的只有两个:

  • GeoJSONTooltip
  • Polygon

第一个方法是用GeoJSON的tooltip参数建立GeoJSONToolTip,如此一来可以在滑鼠移到任意里界时,就显示该里的资讯。但是缺点就是格式无法自定,而且滑鼠不能移走,不然资讯会跳掉。

第二个方法则是用Polygon。因为Polygon本身有提供popup的参数,因此需要解析GeoJSON文件中每一个资料,个别建立polygon。若要统一管理,可建立FeatureGroup纳入所有图层。

GoeJSONTooltip

import folium

myMap = folium.Map([22.73444963475145, 120.28458595275877], zoom_start=14)
# 建立tooltip,并设定要显示的栏位,可自订栏位名称
villTooltip = folium.GeoJsonTooltip(['TV_FALL', 'AREA'], ['名称', '面积'])
kaoVillage = folium.GeoJson('../../dist/mapdata/KaoVillageRange.json', tooltip = villTooltip).add_to(myMap)
myMap.fit_bounds(kaoVillage.get_bounds())
myMap.save('Folium_KaoVill.html')

此时将滑鼠移到里界的范围中,就会显示该里的名称及资讯了。

folium_KaoVillGeoJsonTooltip

Polygon

使用polygon这个方法故然可行,但是因为polygon读取座标的顺序刚好跟geojson格式的顺序相反,因此若直接使用,无法顺利画出我们要的图层,可参考lon lat lon lat lon – macwright.org,目前正在尝试建立转换的方法,或许可以再下一篇文章分享。

剩下最后两天!!

Django允许使用者拥有自己的资料

使用者应该要能输入他们自己的资料,因此我们配置一个系统确哪些资料属于哪个使用者所有,然后限制某些页面的存取权,以便让使用者输入自己的资料

限制存取

Django提供了@login_required修饰模式来让我们轻松实现这个目标

  • 对Topics页面的存取

每个主题都是由使用者所建立拥有的,因此只有登入的使用者才能够请求主题页面,我们要打开learning_logs资料夹中的views.py来汇入login_required函数,再将login_required函数当作修饰模式用在topics视图函数,如下

--省略--
from django.contrib.auth.decorators import login_required

--省略--

@login_required # 会检测使用者是否已经登入,若已登入才会执行topics()的程序
def topics(request):
    topics = Topic.objects.filter(owner=request.user).order_by('date_added')
    context = {'topics': topics}
    return render(request, 'learning_logs/topics.html', context)

要做到重新导向到登入页面,我们还得在setting.py的尾端新增以下代码

# my settings
LOGIN_URL='/users/login'

新增完后我们可以开启我们的网页来测试看看是不是登出后就没办法点进去Topics里

  • 对整个learning log 页面进行存取

在learning log项目中我们要对主页、登入注册页面和登出页面是不设限制的,但对其他都要设定存取权,所以我们要在learning logs资料夹中的views.py将@login_required套用到index()以外的每个视图,如下

@login_required
def topics(request):
--省略--
@login_required
def topic(request, topic_id):
--省略--
@login_required
def new_topic(request):
--省略--
@login_required
def new_entry(request, topic_id):
--省略--
@login_required
def edit_entry(request, entry_id):
--省略--

把资料连接到特定使用者

现在我们要把资料连接到提交他们的使用者上,我们只需要把最高级层的资料连接到使用者,这样低级层的资料就会跟着连接到所属的使用者

修改Topic模型

我们要来修改Topic模型,在其中新增一个外部键连接使用者,随后我们会修改迁移资料库,最后还必须对视图做一些修改,让它们只显示与目前登入使用者相关有连接的资料,我们要开启models.py来新增一些代码,如下

from django.db import models
from django.contrib.auth.models import User # 新增区块

class Topic(models.Model):
--省略--    
    owner=models.ForeignKey(User,on_delete=models.CASCADE) # 新增区块
--省略--   

迁移资料库

我们从执行python manage.py makemigrations learning_logs命令后,Django会给我们两个选择,马上提供预设值,或是退出并在models.py中新增预设值,在这里我们选了第一种,因此Django让我们输入预设值,为了要把所有现有的主题与原本管理者weiting2关联起来,我们在这里输入1的使用者ID,如下

现在我们可以进行迁移了,我们要在虚拟环境下输入python manage.py migrate,如下

限制只能存取属于自己的主题

目前不管以哪个身分登入,都能看到所有主题,我们要来修改成限制存取属于自己的主题,打开views.py对topics函数新增代码,如下

@login_required
def topics(request):
    topics = Topic.objects.filter(owner=request.user).order_by('date_added')
    context = {'topics': topics}
    return render(request, 'learning_logs/topics.html', context)

当使用者登入后,request物件会有个user属性,此属性储存了关于该使用者的资讯,Topic.objects.filter(owner=request.user)这段代码会让Django只从资料库中取得其owner属性为目前使用者的Topic物件

保护使用者的主题

我们还没真的限制存取主题页面,所以任何已登入的使用者都可输入类似像http://localhost:8000/topics/1 这样的URL来存取对应的主题页面,为了修改这样的问题,我们在topics()视图函数取得请求的纪录项目之前先执行检测,如下

--省略--
from django.http import HttpResponseRedirect,Http404
--省略--

@login_required
def topic(request, topic_id):
    """Show a single topic, and all its entries."""
    topic = Topic.objects.get(id=topic_id)
    if topic.owner != request.user: # 请求主题与现在使用者不符合
        raise Http404 # 返回错误页面
    
    entries = topic.entry_set.order_by('-date_added')
    context = {'topic': topic, 'entries': entries}
    return render(request, 'learning_logs/topic.html', context)

保护edit_entry页面

edit_entry页面的URL格式为 http://localhost:8000/edit_entry/entry_id ,其中entry_id是个数字,让我们来保护这个页面,这样任何人都不能以这个URL来存他人的纪录项目,如下

@login_required
def edit_entry(request, entry_id):
    entry = Entry.objects.get(id=entry_id)
    topic = entry.topic
    if topic.owner != request.user:
        raise Http404
--省略--

把新主题关连到目前使用者

目前我们新增主题页面是有问题的,因为它还不会把新主题与任何特定使用者关联起来,我们可以试着新增主题,会看到一个IntegrityError错误讯息,指出learning_logs_topic.user_id不能是NULL,因此我们可以透过request物件存取目前使用者,开启views.py加入以下代码

@login_required
def new_topic(request):
    if request.method != 'POST':
        form = TopicForm()
    else:
        form = TopicForm(request.POST)
        if form.is_valid():
            new_topic = form.save(commit=False) # 新增区块
            new_topic.owner = request.user # 新增区块
            new_topic.save() # 新增区块
            return HttpResponseRedirect(reverse('learning_logs:topics'))
--省略--

第八行的地方我们是先呼叫form.save()并把commit=False当引数传入,这是因为我们先修改新主题,再把它储存进资料库,随后把新主题的OWNER属性设为目前使用者,最后对刚定义的主题实例呼叫save()

附上排版较精美的
HackMD网址:https://hackmd.io/pdXIhZM6Q8yjwN3BjiJBKQ?both

今天结束,各位明天见


资料来源:<>-Eric Matthes着/H&C译

Django建立使用者帐号

我们今天要来设定一个使者登录注册和身分验证的系统,让使用者可以注册帐号,并能够登入和登出

user应用程序

首先我们要先用startapp命令来建立名为users应用程序,如下
python manage.py startapp users

然后我们可以看现在的learning_log目录多了users的应用程序,也可以看users目录里面的结构跟learning_logs相同,如下

把users加到setting.py中

将users应用程序引入整个项目中,如下

INSTALLED_APPS = (
--省略--
    'learning_logs',
    'users', # 新增区块
)

放入users应用程序的URL

我们加了一行代码来引入users应用程序中的urls.py档,这行代码与任何以users为开头的URL都比对符合,如下

from django.urls import path, include
from django.contrib import admin

urlpatterns = [
    path('admin/', admin.site.urls),
    path('users/', include('users.urls')), # 新增区块
    path('', include('learning_logs.urls')),
]

登入

登入的URL模式

我们现在要来做login的页面,我们会在users资料夹里面新增一个urls.py档,如下

from django.conf.urls import url
from django.urls import path
from django.contrib.auth.views import LoginView
from . import views

app_name='users'

urlpatterns = [    
    path('login/', LoginView.as_view(template_name='users/login.html'), name="login"),
]

上面代码我们先在第三行汇入LoginView视图,第九行是登入页面的URL模式与URL http://localhost:8000/users/login 相配符合

登入模板

使用者请求登入页面时,Django会使用预设login视图来处理,但我们还是需要为这个网页提供模板,首先要在users资料夹里建立一个templates资料夹,在templates资料夹中再建立一个users资料夹,里面新增一个login.html,如下

{% extends "learning_logs/base.html" %}

{% block content %}

  {% if form.errors %}
    

Your username and password didn't match. Please try again.

{% endif %}
{% csrf_token %} {{ form.as_p }}
{% endblock content %}

上面代码第五行是如果表单errors属性被设定,就会显示一条错误讯息,回报输入的使用者名称-密码对与资料库中储存的都不相符,第十四行这里放入一个隐藏的表单元素next,其中的引数会告知Django在使用者成功登入后会重新导向主页

连结到登入页面

在base.html中新增连到login页面的连结,如下

--省略-- {% if user.is_authenticated %} Hello, {{ user.username }}. {% else %} log in {% endif %}

{% block content %}{% endblock %}

上面代码第三和第四行是如果使用者已登入就不会显示连结

登入页面如下

未登入使用者会先看到log in 连结

点进去后可以输入使用者帐号和密码

登出

我们现在要来做的是登出,我们不用建立登出页面,至要让使用者按一个连结就可以登出并返回主页

登出的URL模式

--省略--
urlpatterns = [
--省略--
    path('logout/', views.logout_view, name='logout'),
]

logout_view()视图模式

汇入Django的logout()函数并呼叫它,再重新导向主页,首先我们要开启users资料夹中的views.py档,如下

from django.shortcuts import render
from django.http import HttpResponseRedirect
from django.contrib.auth import logout # 汇入logout函数
from django.urls import reverse

def logout_view(request): # 呼叫logout函数,它会把request当作引数,然后重新导向主页
    logout(request)
    return HttpResponseRedirect(reverse('learning_logs:index'))

连结到logout视图

在base.html中新增连到logout的连结,如下

--省略-- {% if user.is_authenticated %} Hello, {{ user.username }}. log out {% else %} --省略-- {% endif %}

{% block content %}{% endblock %}

将第五行的logout连结放在if区块内是因为只有在使用者登入时才能看到logout连结

登出连结如下

登入注册页面

这里要建立让使用者可以登录注册的页面,我们会使用Django预设提供的UserCreationForm表单

登入注册的URL模式

--省略--
urlpatterns = [
--省略--
    path('register/', views.register, name='register'),
]

register()视图函数

在登入注册页面第一次被请求时,register视图函是需要显是一个空的物注册表单,并在使用者填好资料提交表单时对它进行处理,如果登入注册成功,这个函数还需要让使用者登入系统,现在我们要开启views.py档来新增一些代码,如下

from django.shortcuts import render
from django.http import HttpResponseRedirect
from django.contrib.auth import logout
from django.urls import reverse
from django.contrib.auth.forms import UserCreationForm
--省略--
def register(request):
    """Register a new user."""
    if request.method != 'POST':  
        form = UserCreationForm()
    else:
        form = UserCreationForm(data=request.POST)
        
        if form.is_valid():
            new_user = form.save()
            authenticated_user = authenticate(username=new_user.username,
                password=request.POST['password1'])
            login(request, authenticated_user)
            return HttpResponseRedirect(reverse('learning_logs:index'))

    context = {'form': form}
    return render(request, 'users/register.html', context)

登入注册模板

新增一个register.html档放在和login.html同一个资料夹,如下

{% extends "learning_logs/base.html" %}

{% block content %}

  
{% csrf_token %} {{ form.as_p }}
{% endblock content %}

登录注册页面的连结

在base.html中新增连到登入页面的连结,如下

--省略-- {% else %} register - --省略-- {% endif %}

{% block content %}{% endblock %}

登入注册连结如下

会看到主页多了register连结

点进来后会看到预设的表单

附上排版较精美的
HackMD网址:https://hackmd.io/k4M00PB4TDKybC0IAJtCQw?both

今天结束,各位明天见


资料来源:<>-Eric Matthes着/H&C译
资料来源:https://stackoverflow.com/questions/51906428/django-cannot-import-login-from-django-contrib-auth-views/51906537
资料来源:https://github.com/ehmatthes/pcc/tree/master/chapter_19#p-439-including-the-urls-from-users

[演算法] 最短路径 (FloydWarshall 演算法)

网路上有各式各样的地图出现,背后的运算就有很多的演算法、资料库和参数来支持。还记得之前讨论过有关图的深度及广度搜寻,就有提到过怎么找最短的路径,而这只是其中最基础的几个概念而已。现今的程序运算其实都更复杂,效率也更高,都是以前科学家的发明,还有现在的工程师的发展,我们才有这些科技可用。

回到今天的主题,来介绍一个号称核心概念只有五行的演算法:Floyd-Warshall 演算法

先来看我们今天要走的图:
https://ithelp.ithome.com.tw/upload/images/20181110/20111557yGffYwiQzg.png
我们现在要找的是任意两个点之间的最短路径,也称作「多源最短路径」。

财报爬虫和分析

前言

今天是铁人的第26天,主要介绍爬取财报的资料,和财报的基本分。

安装的套件

需要的套件requests,如果没有这个套件用以下的语法新增

pip install requests

预先载入套件

介绍底下的图表之前先预先载入需要的套件

# basic
import numpy as np
import pandas as pd

# get data
import pandas_datareader as pdr

# visual
import matplotlib.pyplot as plt
%matplotlib inline
import seaborn as sns

#requests
import requests

爬取财报的资料 – 营益

资料来源

网站:公开资讯观测站
爬取的内容:

  1. 营益分析表

要分析的function

def remove_td(column):
    remove_one = column.split('<')
    remove_two = remove_one[0].split('>')
    return remove_two[1].replace(",", "")

def translate_dataFrame(response):
     # 拆解内容
    table_array = response.split('1):
            code = remove_td(td_array[1])
            name = remove_td(td_array[2])
            revenue  = remove_td(td_array[3])
            profitRatio = remove_td(td_array[4])
            profitMargin = remove_td(td_array[5])
            preTaxIncomeMargin = remove_td(td_array[6])
            afterTaxIncomeMargin = remove_td(td_array[7])
            if(type(code) == float):
                data.append([code, revenue, profitRatio, profitMargin, preTaxIncomeMargin, afterTaxIncomeMargin])
                index.append(name)
            if( i == 1 ):
                column.append(code)
                column.append(revenue)
                column.append(profitRatio)
                column.append(profitMargin)
                column.append(preTaxIncomeMargin)
                column.append(afterTaxIncomeMargin)
                
    return pd.DataFrame(data=data, index=index, columns=column)

抓取资料的程序

def financial_statement(year, season):

    if year >= 1000:
        year -= 1911
        
    url = 'http://mops.twse.com.tw/mops/web/ajax_t163sb06'
    form_data = {
        'encodeURIComponent':1,
        'step':1,
        'firstin':1,
        'off':1,
        'TYPEK':'sii',
        'year': year,
        'season': season,
    }

    response = requests.post(url,form_data)
    response.encoding = 'utf8'
    
    df = translate_dataFrame(response.text)
    return df

stock = financial_statement(107,2)
stock = stock.astype(float)

抓取到的资料如下图
https://ithelp.ithome.com.tw/upload/images/20181106/2011139090XgNFaF3f.png

营利分析

抓取完资料之后要来做简单几个例子的分析,示范如下

  • 只拿某个股票的营利
stock.loc['一零四']
# 输出结果
公司代号          3130
营业收入        782.06
毛利率(%)       89.89
营业利益率(%)     21.27
税前纯益率(%)     22.96
税后纯益率(%)     18.55
Name: 一零四, dtype: object
  • 拿两个以上的股票
stock.loc[['华新科','一零四']]

https://ithelp.ithome.com.tw/upload/images/20181106/20111390llGKr5S41R.png

  • 画出毛利率的分布图
plt.rcParams['axes.unicode_minus']=False
fig = plt.figure(figsize=(10, 6))
stock['毛利率(%)'].hist(bins=range(-100,100) , label="毛利率(%)")
plt.legend()

https://ithelp.ithome.com.tw/upload/images/20181106/20111390kyvNiBVLhm.png

  • 利用DataFrame来选股
    设定以下的条件
  1. 毛利率(%)大于30
  2. 营业利益率(%)大于30
cond1 = stock['毛利率(%)'] > 30
cond2 = stock['营业利益率(%)'] > 30
stock[cond1 & cond2]

https://ithelp.ithome.com.tw/upload/images/20181106/201113900MzVC5Su5N.png


心得

今天学习了如何抓取财报的资料,并且转成DataFrame,再用DataFrame来设条件选取想要的股票。这是一个非常基础的示范。各位读者也可以设定自己想要的条件来抓取股票

参考网站

FinLab

之前的章节

[演算法] Kmeans 分群 (Kmeans Clustering)

先说说什么是分群?分群就是对所有数据进行分组,将相似的数据归类为一起,每一笔数据的能有一个分组,每一组称作为群集 (Cluster)。那分类根据什么来定义,常用距离来做运算。

K-means 分群 (K-means Clustering),其实就有点像是以前学数学时,找重心的概念。
概念是这样的:

  1. 我们先决定要分k组,并随机选k个点做群集中心。
  2. 将每一个点分类到离自己最近的群集中心(可用直线距离)。
  3. 重新计算各组的群集中心(常用平均值)。

反复 2、3 动作,直到群集不变,群集中心不动为止。

而k-means分群的时间复杂度为 O(NKT) , N 是数据数量, K 是群集数量, T 是重复次数。我们无法预先得知群集数量、重复次数。数据分布情况、群集中心的初始位置,都会影响重复次数,运气成份很大。