2024/10/15

Asynchronous Crawler In PHP, Node.js And Python

久久就要複習一下爬蟲寫法,不然不常用就生疏了,針對三種語言寫個 async 的版本吧。

node.js

const axios = require('axios');  
const urls = [  
    'https://jsonplaceholder.typicode.com/todos/1',  
    'https://jsonplaceholder.typicode.com/todos/2',  
    'https://jsonplaceholder.typicode.com/todos/3',  
];  
  
(async () => {  
    try {  
        const requests = urls.map(url => axios.get(url));  
        const responses = await Promise.all(requests);  
  
        responses.forEach(response => {  
            console.log(response.data.id);  
        });  
    } catch (error) {  
        console.error(error.message);  
    }  
})();

Python

import aiohttp  
import asyncio  
  
urls = [  
    'https://jsonplaceholder.typicode.com/todos/1',  
    'https://jsonplaceholder.typicode.com/todos/2',  
    'https://jsonplaceholder.typicode.com/todos/3',  
]  
  
  
async def fetch_url(session, url):  
    async with session.get(url) as response:  
        return await response.json()  
  
  
async def main():  
    async with aiohttp.ClientSession() as session:  
        tasks = [fetch_url(session, url) for url in urls]  
        responses = await asyncio.gather(*tasks)  
  
        for responses in responses:  
            print(responses.get('id'))  
  
  
if __name__ == '__main__':  
    asyncio.run(main())

PHP

<?php

use GuzzleHttp\Client;
use GuzzleHttp\Promise\Utils;

include "vendor/autoload.php";

$urls = [
    'https://jsonplaceholder.typicode.com/todos/1',
    'https://jsonplaceholder.typicode.com/todos/2',
    'https://jsonplaceholder.typicode.com/todos/3',
];

$client = new Client([
    'verify' => false,
]);
$promises = [];

foreach ($urls as $url) {
    $promises[] = $client->getAsync($url);
}

$results = Utils::all($promises)->wait();

foreach ($results as $result) {
    echo json_decode($result->getBody()->getContents(), true)['id'], PHP_EOL;
}

2024/10/09

(G)I-DLE 2024 Concert In Taipei

我蠻常看演唱會的,但這是我第一次看韓團的演唱會,太喜歡 (G)I-DLE 這個團體了,歌好聽,五個團員又跟瘋子一樣好笑,其中我的本命是 Minnie,迷人的雙眼,有磁性的聲音,親切的外表,完全無法招架。

應該很多人都是從這部影片入坑的,Minnie 非常有語言天分,其他團員雖然也算努力學中文,但 Minnie 講起中文幾乎沒有口音,而且有正確的邏輯理解,這段跟屬於中國人的雨琦吵架居然用邏輯輾壓 😙

她那磁性十足的嗓音我個人覺得她 solo 是完全沒問題的,真的是太可愛了,難能可貴的是,她家在泰國是有錢的家族,(G)I-DLE Minnie竟是隱藏版泰國公主?,有一集韓綜有去拍她家,跟皇宮差不多,但接觸過她的人都說她沒有驕氣,漂亮又可愛的代表。

入口

費盡九牛二虎之力搶到票,2024/10/06 終於讓我一償夙願可以看到他們現場表演。

Taco

由於演唱會是 17:30 開始,所以先吃點東西墊墊肚子,小巨蛋裡面是不可以吃東西的。

演唱會內部

進到演唱會現場超興奮的,我參加過那麼多演唱會,第一次遇到光是看 MV 就全場大合唱的情況,真的超級 high。

我錄了幾首我特別喜歡的歌的片段,大部分的時間都是專心用雙眼雙耳享受現場表演,他們現場實在是太迷人了,由於團內有兩位會講中文的人,跟現場的人互動環節滿點,Yuqi 跟舒華又很搞笑,聊天環節笑料不斷,我上一次看的演唱會是鄭中基,鄭的情歌居多,所以整場大部分都是專心聽歌,冷靜感動,但這場是從頭 high 到尾,因為他們可以帶動氣氛的歌太多了,大部分時間都處於爆裂爽的情況,非常非常的好玩。

這是我聽過這麼多場演唱會以來第一次有結束後不想離開的情緒,就很捨不得,坐我妹旁邊的歌迷還哭了,大家都很依依不捨吧,中間有穿插幾個換裝墊檔的影片,其中一個影片是用 QA 問每個團員一些事情,讓我印象深刻的是,當問到娟總 (G)I-DLE 的演唱會代表什麼時,娟總回答:2024 最開心的一天,我無比的贊同,那天我真的非常非常非常的開心,下次他們再來開演唱會的話,我一定會買下面的座位,我想要得到跟他們互動的機會,真的太喜歡他們了。

2024/07/11

Create Local SSL For Development

內部服務使用 SSL 相關解法

🟣 建立自簽憑證

  • domain name: example.test
  • ip: 10.10.10.22

🎈 此方法不需要擁有真實 domain,但會需要去每個 user 電腦配置內容。

💬 建立 ca 憑證

openssl genrsa -out ca.key 2048
openssl req -x509 -new -nodes -key ca.key -sha256 -days 3650 -out ca.pem

建立流程 Common Name 最為重要,不可以填錯。

💬 建立 service 憑證需求檔案

openssl genrsa -out server.key 2048
openssl req -new -key server.key -out server.csr

💬 產出 server 憑證

先建立一個文件 server.ext

authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
subjectAltName = @alt_names
[alt_names]
DNS.1 = example.test
DNS.2 = *.example.test

💬 產生服務 crt

openssl x509 -req -in server.csr -CA ca.pem -CAkey ca.key -CAcreateserial -out server.crt -days 3650 -sha256 -extfile server.ext

💬 配置 nginx.conf

server {
    listen 443 ssl;
    root /var/www/html;
    server_name: example.test;
    index index.html;
    ssl_certificate /etc/ssl/server.crt;
    ssl_certificate_key /etc/ssl/server.key;
}

💬 將 CA 憑證匯入 Windows

Windows Key + r 叫出執行視窗,輸入 certmgr.msc

  1. 選擇受信任的根憑證授權單位
  2. 按下滑鼠右鍵選擇 所有工作 > 匯入
  3. 選擇下一步。
  4. 將檔案類型選擇所有檔案,並且選擇 ca.pem,接著一路下一步以及同意到結束。

💬 修改 user hosts

Windows 的路徑在 C:\Windows\System32\drivers\etc\hosts

填入 10.10.10.22 example.test

這時候在網址打 https://example.test 應可以順利使用 https 連線 server 並且不予以警告。

🟣 其他方法

💬 使用真實憑證

ACME 驗證方式

letsencrypt 可以使用不直連服務驗證的方式發放憑證,可參考文件進行 DNS 認證,但這個方法還是必須有真實 domain name。

💬 Reverse proxy server

使用一台具有 domain public 可用的 reverse server,將他配置好 SSL 資源後將 domain 轉到區網 service,並且限制可連線的區網 IP。

🟣 參考網站

2024/05/22

Different copy behavior in rsync scp cp and mv

在 Linux 的環境內,我們常常會需要搬移目錄或檔案,不管是在內部或者是 remote,搬移的時候尾巴有沒有 / 這個符號在幾個常用跟搬移有關的指令實際行為有很大的差異,以下面的結構舉例。

src/first.txt
src/second/second.txt

dest/

src 是來源目錄,dest 是目標目錄。

cp

command result
cp -rv src dest/ dest/src/first.txt
dest/src/second/second.txt
cp -rv src/ dest/ dest/src/first.txt
dest/src/second/second.txt
cp -rv src/* dest/ dest/first.txt
dest/second/second.txt

cp 這個指令不會隨 / 有所影響,要不要 src 這層取決於你有沒有指定他以下的內容進行複製。

scp

command result
scp -r src dest/ dest/src/first.txt
dest/src/second/second.txt
scp -r src/ dest/ dest/src/first.txt
dest/src/second/second.txt
scp -r src/* dest/ dest/first.txt
dest/second/second.txt

scp 的複製行為跟 cp 是一模一樣的。

rsync

command result
rsync -rv src dest/ dest/src/first.txt
dest/src/second/second.txt
rsync -rv src/ dest/ dest/first.txt
dest/second/second.txt
rsync -rv src/* dest/ dest/first.txt
dest/second/second.txt

rsync 是會受到 src 後方有沒有 / 影響結果的。

mv

command result
mv src dest/ dest/src/first.txt
dest/src/second/second.txt
mv src/ dest/ dest/src/first.txt
dest/src/second/second.txt
mv src/* dest/ dest/first.txt
dest/second/second.txt

總結來講,cpscpmv 這三個搬運的行為是一致的,會有不同影響的只有 rsync,所以使用 rsync 時要比較注意一點,也可以說你要不包含目錄的話,四個指令就都加 /*,要包含的話就尾巴不要 /,這樣比較好記。

2024/04/10

Windows PowerShell Theme

無意間看到了這個影片How to set up PowerShell prompt with Oh My Posh on Windows 11,沒想到 PowerShell 也可以搞得這麼漂亮,於是參照裡面的設定,幫自己的 PowerShell 做了喜歡的畫面改動。

  1. 安裝 Scoop
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
Invoke-RestMethod -Uri https://get.scoop.sh | Invoke-Expression
  1. 安裝 Nerd Fonts
scoop bucket add nerd-fonts
scoop install Hack-NF


安裝 Nerd Fonts 後,設定 PowerShell 的字體為 Hack Nerd Font,字型大小 11,透明度 85%。

  1. 安裝 Terminal Icons
scoop bucket add extras
scoop install terminal-icons
  1. 安裝 Oh My Posh
scoop install https://github.com/JanDeDobbeleer/oh-my-posh/releases/latest/download/oh-my-posh.json
  1. 安裝 fzf
scoop install fzf
Install-Module -Name PSFzf
  1. 安裝 ZLocation
Install-Module -Name ZLocation -Force
  1. 配置個人環境變數
nvim $PROFILE.CurrentUserAllHosts

Config 內容

Import-Module -Name Terminal-Icons
Import-Module -Name ZLocation

oh-my-posh init pwsh --config '~/AppData/Local/Programs/oh-my-posh/themes/remk.omp.json' | Invoke-Expression

Set-Alias ll dir
Set-Alias ls ~/scoop/shims/ls.exe
Set-Alias find ~/scoop/shims/find.exe

Set-PsFzfOption -PSReadlineChordProvider 'Ctrl+t' -PSReadlineChordReverseHistory 'Ctrl+r'

我選擇了 remk 這個 theme,安裝了套件 busybox 讓他更像 Linux,也同時安裝了 versions

Final Theme
於是我就得到了美美的 terminal。

reference

2024/03/25

Delete None Keeping Files Part II

之前寫過這篇 Delete None Keeping Files,在某個目錄依照建日保留最新的 N 個檔案,有個新改良的寫法或許更好懂。

#!/bin/bash

KEEP=10
DIRECTORY=/data/

find "$DIRECTORY" -maxdepth 1 -type f -name "*.log" -printf "%T@ %p\n" | sort -r | tail -n +$(($KEEP+1)) | awk '{print $NF}' | xargs rm -f

來解釋各段參數:

  • find "$DIRECTORY":尋找目標目錄
  • -maxdepth 1:只找該目錄第一層
  • -type f:只找尋檔案,不列入目錄
  • -name '*.log':找尋副檔名為 log 的檔案
  • -printf "%T@ %p\n":使用 printf 輸出,輸出方式為建立的時間戳 + 檔名
  • sort -r:依照時間戳逆排序
  • tail -n +$(($KEEP+1)):列出你要保留的檔案數以外的檔案
  • awk '{print $NF}':只抓出檔案路徑
  • xargs rm -f:刪除該檔案

這樣的寫法應該是萬無一失了。

2023/11/26

PHP Quality

要兼顧 PHP 開發品質,在 commit 之前可以用一些套件檢測,不管是否有符合 coding convention 或是有沒有語法上的錯誤,都可以預防性的監測。

composer require --dev phpstan/phpstan squizlabs/php_codesniffer friendsofphp/php-cs-fixer phpmd/phpmd phpunit/phpunit brianium/paratest

搭配 Makefile 可以減少每次執行要打的指令,此配置是針對 Laravel,可依照不同環境內容變動。

Makefile
fix-code-format:
	vendor/bin/php-cs-fixer fix --verbose

code-style-check:
	vendor/bin/phpstan analyse
	vendor/bin/phpcs --standard=PSR12 app tests
	vendor/bin/php-cs-fixer fix --dry-run
	vendor/bin/phpmd app,config,database,routes,tests text phpmd.xml

test: code-style-check
	vendor/bin/paratest --colors --processes 4 --runner=WrapperRunner
phpstan.neon
parameters:
  paths:
    - app
  excludePaths:
    - app/Http/Middleware/Authenticate.php
    - app/Providers/RouteServiceProvider.php

  level: 5

  parallel:
    processTimeout: 60.0
    maximumNumberOfProcesses: 32
    minimumNumberOfJobsPerProcess: 2

  checkMissingIterableValueType: false
.php-cs-fixer.php
<?php

use PhpCsFixer\Config;
use PhpCsFixer\Finder;

$rules = [
    'array_syntax' => ['syntax' => 'short'],
    'binary_operator_spaces' => ['default' => 'single_space'],
    'blank_line_after_namespace' => true,
    'blank_line_after_opening_tag' => true,
    'blank_line_before_statement' => true,
    'cast_spaces' => true,
    'class_definition' => true,
    'declare_equal_normalize' => true,
    'elseif' => true,
    'encoding' => true,
    'full_opening_tag' => true,
    'function_declaration' => true,
    'type_declaration_spaces' => true,
    'lowercase_cast' => true,
    'heredoc_to_nowdoc' => true,
    'include' => true,
    'indentation_type' => true,
    'lowercase_keywords' => true,
    'magic_constant_casing' => true,
    'method_argument_space' => true,
    'phpdoc_separation' => false,
    'native_function_casing' => true,
    'no_alias_functions' => true,
    'no_blank_lines_after_class_opening' => true,
    'no_blank_lines_after_phpdoc' => true,
    'multiline_whitespace_before_semicolons' => ['strategy' => 'no_multi_line'],
    'no_closing_tag' => true,
    'no_empty_phpdoc' => true,
    'no_empty_statement' => true,
    'no_leading_import_slash' => true,
    'no_leading_namespace_whitespace' => true,
    'no_multiline_whitespace_around_double_arrow' => true,
    'no_short_bool_cast' => true,
    'no_singleline_whitespace_before_semicolons' => true,
    'no_spaces_after_function_name' => true,
    'no_spaces_around_offset' => ['positions' => ['inside']],
    'spaces_inside_parentheses' => true,
    'no_trailing_comma_in_singleline' => true,
    'no_trailing_whitespace' => true,
    'no_trailing_whitespace_in_comment' => true,
    'no_unneeded_control_parentheses' => true,
    'no_unreachable_default_argument_value' => true,
    'no_unused_imports' => true,
    'no_useless_return' => true,
    'no_whitespace_before_comma_in_array' => true,
    'no_whitespace_in_blank_line' => true,
    'normalize_index_brace' => true,
    'not_operator_with_successor_space' => true,
    'object_operator_without_whitespace' => true,
    'ordered_class_elements' => true,
    'ordered_imports' => ['sort_algorithm' => 'alpha'],
    'phpdoc_indent' => true,
    'phpdoc_line_span' => ['const' => 'multi', 'method' => 'multi', 'property' => 'single'],
    'phpdoc_no_access' => true,
    'phpdoc_no_useless_inheritdoc' => true,
    'phpdoc_order' => true,
    'phpdoc_scalar' => true,
    'phpdoc_single_line_var_spacing' => true,
    'phpdoc_to_comment' => true,
    'phpdoc_trim' => true,
    'phpdoc_no_alias_tag' => ['replacements' => ['type' => 'var']],
    'phpdoc_types' => true,
    'phpdoc_var_without_name' => true,
    'no_mixed_echo_print' => ['use' => 'echo'],
    'self_accessor' => true,
    'short_scalar_cast' => true,
    'simplified_null_return' => true,
    'single_blank_line_at_eof' => true,
    'blank_lines_before_namespace' => true,
    'single_class_element_per_statement' => true,
    'single_import_per_statement' => true,
    'single_line_after_imports' => true,
    'single_quote' => true,
    'space_after_semicolon' => true,
    'standardize_not_equals' => true,
    'switch_case_semicolon_to_colon' => true,
    'switch_case_space' => true,
    'ternary_operator_spaces' => true,
    'trailing_comma_in_multiline' => ['elements' => ['arrays']],
    'trim_array_spaces' => true,
    'unary_operator_spaces' => true,
    'visibility_required' => true,
    'whitespace_after_comma_in_array' => true,
    'concat_space' => ['spacing' => 'one'],
    'single_space_around_construct' => true,
    'control_structure_braces' => true,
    'braces_position' => true,
    'control_structure_continuation_position' => true,
    'declare_parentheses' => true,
    'statement_indentation' => true,
];

$excludes = [
    'bootstrap/cache',
    'storage',
    'vendor',
    'node_modules',
];

$finder = PhpCsFixer\Finder::create()
                           ->in(__DIR__)
                           ->exclude($excludes)
                           ->notName('*.xml')
                           ->notName('*.yml');

$config = new PhpCsFixer\Config();
$config->setRiskyAllowed(true)
       ->setIndent('    ')
       ->setLineEnding("\n")
       ->setRules($rules)
       ->setUsingCache(true)
       ->setFinder($finder);

return $config;
phpmd.xml
<?xml version="1.0" encoding="UTF-8"?>
<ruleset name="Netask rule set"
    xmlns="http://pmd.sf.net/ruleset/1.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://pmd.sf.net/ruleset/1.0.0 http://pmd.sf.net/ruleset_xml_schema.xsd"
    xsi:noNamespaceSchemaLocation="http://pmd.sf.net/ruleset_xml_schema.xsd">
<description>Netask code style rule set
</description>
<rule ref="rulesets/codesize.xml/CyclomaticComplexity"/>
<rule ref="rulesets/codesize.xml/NPathComplexity"/>
<rule ref="rulesets/codesize.xml/ExcessiveMethodLength"/>
<rule ref="rulesets/codesize.xml/ExcessiveClassLength"/>
<rule ref="rulesets/codesize.xml/ExcessiveParameterList"/>
<rule ref="rulesets/codesize.xml/ExcessivePublicCount">
    <properties>
        <property name="minimum" value="30"/>
    </properties>
</rule>
<rule ref="rulesets/codesize.xml/TooManyFields">
    <properties>
        <property name="maxfields" value="20"/>
    </properties>
</rule>
<rule ref="rulesets/codesize.xml/TooManyMethods"/>
<rule ref="rulesets/codesize.xml/ExcessiveClassComplexity">
    <properties>
        <property name="maximum" value="30"/>
    </properties>
</rule>
<rule ref="rulesets/controversial.xml"/>
<rule ref="rulesets/design.xml"/>
<rule ref="rulesets/naming.xml/ShortVariable"/>
<rule ref="rulesets/naming.xml/LongVariable">
    <properties>
        <property name="maximum" value="30"/>
    </properties>
</rule>
<rule ref="rulesets/naming.xml/ShortMethodName">
    <properties>
        <property name="minimum" value="2"/>
    </properties>
</rule>
<rule ref="rulesets/naming.xml/ConstructorWithNameAsEnclosingClass"/>
<rule ref="rulesets/naming.xml/ConstantNamingConventions"/>
<rule ref="rulesets/naming.xml/BooleanGetMethodName"/>
<rule ref="rulesets/unusedcode.xml/UnusedPrivateField" />
<rule ref="rulesets/unusedcode.xml/UnusedLocalVariable" />

<exclude-pattern>app/Console/Kernel.php</exclude-pattern>
<exclude-pattern>app/Services/Service.php</exclude-pattern>
<exclude-pattern>tests/TestCase.php</exclude-pattern>
</ruleset>
phpunit.xml
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="./vendor/phpunit/phpunit/phpunit.xsd"
         bootstrap="vendor/autoload.php"
         colors="true"
>
    <testsuites>
        <testsuite name="Unit">
            <directory suffix="Test.php">./tests/Unit</directory>
        </testsuite>
        <testsuite name="Feature">
            <directory suffix="Test.php">./tests/Feature</directory>
        </testsuite>
    </testsuites>
    <coverage processUncoveredFiles="true">
        <include>
            <directory suffix=".php">./app</directory>
        </include>
    </coverage>
    <php>
        <server name="APP_ENV" value="testing"/>
        <server name="BCRYPT_ROUNDS" value="4"/>
        <server name="CACHE_DRIVER" value="array"/>
        <!-- <server name="DB_CONNECTION" value="sqlite"/> -->
        <!-- <server name="DB_DATABASE" value=":memory:"/> -->
        <server name="MAIL_MAILER" value="array"/>
        <server name="QUEUE_CONNECTION" value="sync"/>
        <server name="SESSION_DRIVER" value="array"/>
        <server name="TELESCOPE_ENABLED" value="false"/>
    </php>
</phpunit>