library(rvest)
library(dplyr)
## 
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
## 
##     filter, lag
## The following objects are masked from 'package:base':
## 
##     intersect, setdiff, setequal, union
library(stringr)

url <- "https://isin.twse.com.tw/isin/C_public.jsp?strMode=2"

library(httr)
library(rvest)

response <- GET(url)
stop_for_status(response)

raw_html <- content(response, "raw")

html_utf8 <- iconv(
  rawToChar(raw_html),
  from = "CP950",
  to = "UTF-8",
  sub = "byte"
)

page <- read_html(html_utf8, encoding = "UTF-8")

tables <- html_elements(page, "table") |>
  html_table(fill = TRUE)

raw_twse <- tables[[which.max(sapply(tables, nrow))]]

head(raw_twse)
## # A tibble: 6 × 7
##   X1                 X2                          X3      X4    X5    X6    X7   
##   <chr>              <chr>                       <chr>   <chr> <chr> <chr> <chr>
## 1 有價證券代號及名稱 國際證券辨識號碼(ISIN Code) 上市日  市場別…… 產業別…… CFIC… "備註"…
## 2 股票               股票                        股票    股票  股票  股票  "股票"…
## 3 1101 台泥         TW0001101004                1962/0… 上市  水泥工業… ESVU… ""   
## 4 1102 亞泥         TW0001102002                1962/0… 上市  水泥工業… ESVU… ""   
## 5 1103 嘉泥         TW0001103000                1969/1… 上市  水泥工業… ESVU… ""   
## 6 1104 環泥         TW0001104008                1971/0… 上市  水泥工業… ESVU… ""
library(dplyr)
library(stringr)

twse_clean <- raw_twse %>%
  mutate(
    stock_id = str_extract(X1, "^[0-9]{4}"),
    name = str_trim(str_remove(X1, "^[0-9]{4}")),
    listing_date = X3,
    industry = X5,
    cfi_code = X6
  ) %>%
  filter(
    !is.na(stock_id),
    cfi_code == "ESVUFR",
    X4 == "上市"
  ) %>%
  select(stock_id, name, listing_date, industry) %>%
  distinct(stock_id, .keep_all = TRUE)

head(twse_clean)
## # A tibble: 6 × 4
##   stock_id name  listing_date industry
##   <chr>    <chr> <chr>        <chr>   
## 1 1101     台泥  1962/02/09   水泥工業
## 2 1102     亞泥  1962/06/08   水泥工業
## 3 1103     嘉泥  1969/11/14   水泥工業
## 4 1104     環泥  1971/02/01   水泥工業
## 5 1108     幸福  1990/06/06   水泥工業
## 6 1109     信大  1991/12/05   水泥工業
nrow(twse_clean)
## [1] 1053
library(httr)
library(jsonlite)
library(dplyr)
library(stringr)

api_url <- "https://api.finmindtrade.com/api/v4/data"

response_fm <- GET(
  api_url,
  query = list(dataset = "TaiwanStockInfo")
)

stop_for_status(response_fm)

result_fm <- fromJSON(
  content(response_fm, "text", encoding = "UTF-8")
)

if (result_fm$status != 200) {
  stop(result_fm$msg)
}

finmind_raw <- as.data.frame(result_fm$data)

head(finmind_raw)
##         industry_category stock_id      stock_name type       date
## 1            電子零組件業     5481            新華 tpex 2020-06-03
## 2        電腦及週邊設備業     5450          寶聯通 tpex 2020-06-03
## 3                  光電業     3629        地心引力 tpex 2020-06-03
## 4              文化創意業     3687          歐買尬 tpex 2020-06-03
## 5              其他電子類     6238            勝麗 tpex 2020-06-13
## 6 上櫃指數股票型基金(ETF)   00835B 第一金科技債10+ tpex 2020-06-24
library(dplyr)
library(stringr)

finmind_clean <- finmind_raw %>%
  filter(
    type == "twse",
    str_detect(stock_id, "^[0-9]{4}$"),
    !str_detect(
      industry_category,
      regex("ETF|ETN|fund|preferred|特別股|受益證券",
            ignore_case = TRUE)
    )
  ) %>%
  transmute(
    stock_id = as.character(stock_id),
    name = stock_name,
    industry = industry_category
  ) %>%
  distinct(stock_id, .keep_all = TRUE)

# Add official listing dates from TWSE
finmind_clean <- finmind_clean %>%
  left_join(
    twse_clean %>% select(stock_id, listing_date),
    by = "stock_id"
  ) %>%
  select(stock_id, name, listing_date, industry)

head(finmind_clean)
##   stock_id   name listing_date     industry
## 1     2241 艾姆勒   2020/08/26     電機機械
## 2     1443   立益   1990/04/21     紡織纖維
## 3     3669   圓展   2011/08/25       光電業
## 4     6165   浪凡   2003/08/04     電子工業
## 5     2429 銘旺科   2000/09/11 電子零組件業
## 6     2459   敦吉   2001/09/17   電子通路業
nrow(finmind_clean)
## [1] 1226
library(dplyr)

# Stocks in TWSE but not in FinMind
only_twse <- anti_join(
  twse_clean,
  finmind_clean,
  by = "stock_id"
)

# Stocks in FinMind but not in TWSE
only_finmind <- anti_join(
  finmind_clean,
  twse_clean,
  by = "stock_id"
)

# Stocks appearing in both datasets
common_stocks <- inner_join(
  twse_clean,
  finmind_clean,
  by = "stock_id",
  suffix = c("_twse", "_finmind")
)

# Comparison summary
comparison <- data.frame(
  Category = c(
    "TWSE total",
    "FinMind total",
    "In both datasets",
    "Only in TWSE",
    "Only in FinMind"
  ),
  Count = c(
    nrow(twse_clean),
    nrow(finmind_clean),
    nrow(common_stocks),
    nrow(only_twse),
    nrow(only_finmind)
  )
)

knitr::kable(comparison)
Category Count
TWSE total 1053
FinMind total 1226
In both datasets 1053
Only in TWSE 0
Only in FinMind 173
# Inspect unmatched FinMind securities
head(only_finmind, 20)
##    stock_id         name listing_date         industry
## 1      2432         倚天         <NA>       通信網路業
## 2      6806     森崴能源         <NA>             其他
## 3      1230       聯成食         <NA>         電器電纜
## 4      4733         上緯         <NA>     化學生技醫療
## 5      1523         開億         <NA>         電機機械
## 6      1606         歌林         <NA>         電器電纜
## 7      1716         永信         <NA>     化學生技醫療
## 8      1601         台光         <NA>         電器電纜
## 9      1311         福聚         <NA>         塑膠工業
## 10     6951      青新-創         <NA>       創新版股票
## 11     8078         華寶         <NA>       通信網路業
## 12     6969 成信實業*-創         <NA>       創新版股票
## 13     2341         英群         <NA>         電子工業
## 14     2418         雅新         <NA>         電子工業
## 15     8008       建興電         <NA> 電腦及週邊設備業
## 16     2411         飛瑞         <NA>         電子工業
## 17     2350         環電         <NA>       其他電子業
## 18     2403         友尚         <NA>       電子通路業
## 19     2396         精碟         <NA>         電子工業
## 20     2391         合勤         <NA>         電子工業
# Check missing listing dates
sum(is.na(finmind_clean$listing_date))
## [1] 173
# Examine unmatched securities
head(only_finmind, 20)
##    stock_id         name listing_date         industry
## 1      2432         倚天         <NA>       通信網路業
## 2      6806     森崴能源         <NA>             其他
## 3      1230       聯成食         <NA>         電器電纜
## 4      4733         上緯         <NA>     化學生技醫療
## 5      1523         開億         <NA>         電機機械
## 6      1606         歌林         <NA>         電器電纜
## 7      1716         永信         <NA>     化學生技醫療
## 8      1601         台光         <NA>         電器電纜
## 9      1311         福聚         <NA>         塑膠工業
## 10     6951      青新-創         <NA>       創新版股票
## 11     8078         華寶         <NA>       通信網路業
## 12     6969 成信實業*-創         <NA>       創新版股票
## 13     2341         英群         <NA>         電子工業
## 14     2418         雅新         <NA>         電子工業
## 15     8008       建興電         <NA> 電腦及週邊設備業
## 16     2411         飛瑞         <NA>         電子工業
## 17     2350         環電         <NA>       其他電子業
## 18     2403         友尚         <NA>       電子通路業
## 19     2396         精碟         <NA>         電子工業
## 20     2391         合勤         <NA>         電子工業
# Check industries of unmatched securities
sort(table(only_finmind$industry), decreasing = TRUE)
## 
##         電子工業           光電業       創新版股票         金融保險 
##               32               16               15               15 
##             其他         半導體業         存託憑證       創新板股票 
##               11               11               11               10 
## 電腦及週邊設備業       生技醫療業     電子零組件業       通信網路業 
##                7                6                5                4 
##         電器電纜         化學工業     化學生技醫療       電子通路業 
##                4                3                3                3 
##       其他電子業         塑膠工業         建材營造         汽車工業 
##                2                2                2                2 
##         貿易百貨         電機機械         數位雲端         紡織纖維 
##                2                2                1                1 
##         綠能環保           航運業         造紙工業 
##                1                1                1
# Verify stock IDs
length(unique(only_finmind$stock_id))
## [1] 173
library(dplyr)

# Keep stocks confirmed by official TWSE list
finmind_final <- finmind_clean %>%
  semi_join(twse_clean, by = "stock_id")

# Check final results
nrow(finmind_final)
## [1] 1053
head(finmind_final)
##   stock_id   name listing_date     industry
## 1     2241 艾姆勒   2020/08/26     電機機械
## 2     1443   立益   1990/04/21     紡織纖維
## 3     3669   圓展   2011/08/25       光電業
## 4     6165   浪凡   2003/08/04     電子工業
## 5     2429 銘旺科   2000/09/11 電子零組件業
## 6     2459   敦吉   2001/09/17   電子通路業
# Save verified FinMind data
write.csv(
  finmind_final,
  "FinMind_Common_Stocks.csv",
  row.names = FALSE,
  fileEncoding = "UTF-8"
)
final_comparison <- data.frame(
  Source = c(
    "TWSE common stocks",
    "FinMind initially filtered",
    "FinMind matched with TWSE",
    "FinMind unmatched records"
  ),
  Number_of_stocks = c(
    nrow(twse_clean),
    nrow(finmind_clean),
    nrow(finmind_final),
    nrow(only_finmind)
  )
)

knitr::kable(final_comparison)
Source Number_of_stocks
TWSE common stocks 1053
FinMind initially filtered 1226
FinMind matched with TWSE 1053
FinMind unmatched records 173

Results and Discussion

The official TWSE ISIN database returned 1,053 listed common stocks after applying the common stock classification and market filters.

The FinMind TaiwanStockInfo dataset initially contained 4,333 securities. After filtering, 1,226 records remained.

A comparison of stock IDs showed that 1,053 securities appeared in both datasets, while 173 appeared only in the filtered FinMind dataset.

The 173 unmatched securities require further verification. Therefore, a TWSE-validated FinMind subset containing 1,053 stocks was also created. Original listing dates were obtained from TWSE because the FinMind dataset does not directly provide this information.

Conclusion

The TWSE and FinMind datasets were successfully downloaded, processed, and compared. All 1,053 securities in the filtered TWSE dataset were matched in FinMind. The remaining 173 FinMind records were documented separately for further investigation.