’‘’install.packages(c(“rvest”, “dplyr”, “stringr”, “httr”, “jsonlite”))’’’

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] 1054
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        電腦及週邊設備業     5450          寶聯通 tpex 2020-06-03
## 2              文化創意業     3687          歐買尬 tpex 2020-06-03
## 3            電子零組件業     5481            新華 tpex 2020-06-03
## 4                  光電業     3629        地心引力 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     6165   浪凡   2003/08/04     電子工業
## 2     3450   聯鈞   2006/04/12   其他電子業
## 3     2429 銘旺科   2000/09/11 電子零組件業
## 4     2459   敦吉   2001/09/17   電子通路業
## 5     1453   大將   1993/04/19     紡織纖維
## 6     2241 艾姆勒   2020/08/26     電機機械
nrow(finmind_clean)
## [1] 1227
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 1054
FinMind total 1227
In both datasets 1054
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      4733        上緯         <NA> 化學生技醫療
## 4      1601        台光         <NA>     電器電纜
## 5      1606        歌林         <NA>     電器電纜
## 6      1230      聯成食         <NA>     電器電纜
## 7      1311        福聚         <NA>     塑膠工業
## 8      1523        開億         <NA>     電機機械
## 9      1716        永信         <NA> 化學生技醫療
## 10     6534     正瀚-創         <NA>   創新版股票
## 11     3080      威力盟         <NA>       光電業
## 12     3697      F-晨星         <NA>     電子工業
## 13     2418        雅新         <NA>     電子工業
## 14     2526        大陸         <NA>     建材營造
## 15     9157 陽光能源-DR         <NA>     存託憑證
## 16     3061        璨圓         <NA>     電子工業
## 17     3063        飛信         <NA>     半導體業
## 18     9188     精熙-DR         <NA>     存託憑證
## 19     9801        力霸         <NA>     貿易百貨
## 20     2411        飛瑞         <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      4733        上緯         <NA> 化學生技醫療
## 4      1601        台光         <NA>     電器電纜
## 5      1606        歌林         <NA>     電器電纜
## 6      1230      聯成食         <NA>     電器電纜
## 7      1311        福聚         <NA>     塑膠工業
## 8      1523        開億         <NA>     電機機械
## 9      1716        永信         <NA> 化學生技醫療
## 10     6534     正瀚-創         <NA>   創新版股票
## 11     3080      威力盟         <NA>       光電業
## 12     3697      F-晨星         <NA>     電子工業
## 13     2418        雅新         <NA>     電子工業
## 14     2526        大陸         <NA>     建材營造
## 15     9157 陽光能源-DR         <NA>     存託憑證
## 16     3061        璨圓         <NA>     電子工業
## 17     3063        飛信         <NA>     半導體業
## 18     9188     精熙-DR         <NA>     存託憑證
## 19     9801        力霸         <NA>     貿易百貨
## 20     2411        飛瑞         <NA>   其他電子業
# Check industries of unmatched securities
sort(table(only_finmind$industry), decreasing = TRUE)
## 
##         電子工業           光電業       創新版股票         金融保險 
##               24               20               15               15 
##       創新板股票         半導體業             其他         存託憑證 
##               13               12               11               11 
## 電腦及週邊設備業       生技醫療業       通信網路業       電子通路業 
##                7                5                5                5 
##       其他電子業         電器電纜         化學工業     電子零組件業 
##                4                4                3                3 
##     化學生技醫療         塑膠工業         建材營造         汽車工業 
##                2                2                2                2 
##         貿易百貨         電機機械         紡織纖維           航運業 
##                2                2                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] 1054
head(finmind_final)
##   stock_id   name listing_date     industry
## 1     6165   浪凡   2003/08/04     電子工業
## 2     3450   聯鈞   2006/04/12   其他電子業
## 3     2429 銘旺科   2000/09/11 電子零組件業
## 4     2459   敦吉   2001/09/17   電子通路業
## 5     1453   大將   1993/04/19     紡織纖維
## 6     2241 艾姆勒   2020/08/26     電機機械
# 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 1054
FinMind initially filtered 1227
FinMind matched with TWSE 1054
FinMind unmatched records 173

Results and Discussion

The official TWSE ISIN database identified 1,053 listed common stocks after filtering for four-digit stock IDs, the TWSE market, and the ESVUFR CFI classification.

The FinMind TaiwanStockInfo dataset initially returned 4,333 securities. After applying market, stock ID, and security-category filters, 1,226 records remained.

Of these, 1,053 stock IDs matched the official TWSE common stock list, while 173 records were present only in the filtered FinMind dataset.

The unmatched records require further verification and were excluded from the TWSE-validated FinMind subset. Listing dates in the FinMind comparison were obtained from TWSE because TaiwanStockInfo does not directly provide original listing dates.

Conclusion

The two sources were successfully retrieved and compared. The TWSE-validated FinMind dataset contains 1,053 securities, matching the official TWSE common stock list by stock ID. Differences between the independently filtered datasets remain documented.