# install.packages(c("rvest", "httr", "jsonlite", "dplyr", "stringr", "tibble"))
library(rvest)
library(httr)
library(jsonlite)
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)
library(tibble)
PART 1: TWSE ISIN (strMode = 2)
cat("-> Fetching data from TWSE ISIN lookup page...\n")
## -> Fetching data from TWSE ISIN lookup page...
url_twse <- "https://isin.twse.com.tw/isin/C_public.jsp?strMode=2"
response_twse <- GET(url_twse, user_agent("Mozilla/5.0"))
stop_for_status(response_twse)
# page is Big5/CP950
page <- read_html(content(response_twse, as = "raw"), encoding = "CP950")
# Select the data table explicitly instead of taking the first table
tbls <- page %>% html_elements("table.h4")
if (length(tbls) == 0) tbls <- page %>% html_elements("table")
cat(" Tables found:", length(tbls), "\n")
## Tables found: 1
stopifnot(length(tbls) > 0)
# header = FALSE so the first row stays as data (we promote it ourselves)
df_raw <- tbls[[1]] %>% html_table(header = FALSE, fill = TRUE)
stopifnot(ncol(df_raw) > 0, nrow(df_raw) > 1)
# First row is the header row
names(df_raw) <- as.character(df_raw[1, ])
df_raw <- df_raw[-1, ]
# code and name are separated by a FULL-WIDTH space (U+3000)
code_name <- as.character(df_raw[[1]])
stock_id <- str_trim(str_extract(code_name, "^[^\u3000 ]+"))
name <- str_trim(str_replace(code_name, "^[^\u3000 ]+[\u3000 ]+", ""))
df_twse_clean <- tibble(
stock_id = stock_id,
name = name,
listing_date = as.character(df_raw[[3]]),
industry = as.character(df_raw[[5]])
) %>%
filter(!is.na(stock_id), str_detect(stock_id, "^\\d{4}$")) %>%
filter(!str_detect(name, "特別股")) %>%
filter(!is.na(industry), industry != "") # ETFs have an empty industry
cat(sprintf(" Extracted %d common stocks from TWSE.\n\n", nrow(df_twse_clean)))
## Extracted 1085 common stocks from TWSE.
PART 2: FinMind API
cat("-> Fetching data from FinMind API...\n")
## -> Fetching data from FinMind API...
response_fm <- GET("https://api.finmindtrade.com/api/v4/data",
query = list(dataset = "TaiwanStockInfo"))
parsed <- fromJSON(content(response_fm, as = "text", encoding = "UTF-8"))
if (is.null(parsed$msg) || parsed$msg != "success") {
stop("Failed to fetch data from FinMind API: ", parsed$msg)
}
df_fm_clean <- parsed$data %>%
rename(name = stock_name, industry = industry_category, listing_date = date) %>%
filter(type == "twse") %>%
filter(str_detect(stock_id, "^\\d{4}$")) %>%
filter(!str_detect(industry, "ETF|ETN|Index|Beneficiary|受益|基金|大盤")) %>%
filter(!str_detect(name, "特別股")) %>%
distinct(stock_id, .keep_all = TRUE) %>%
select(stock_id, name, listing_date, industry)
# Sanity check: compare TWSE and FinMind listing dates
chk <- inner_join(df_twse_clean, df_fm_clean, by = "stock_id", suffix = c("_twse", "_fm")) %>%
mutate(same = as.Date(gsub("/", "-", listing_date_twse), optional = TRUE) ==
as.Date(listing_date_fm, optional = TRUE))
cat(sprintf(" Listing date matches TWSE for %.1f%% of shared stocks.\n",
100 * mean(chk$same, na.rm = TRUE)))
## Listing date matches TWSE for 0.0% of shared stocks.
cat(sprintf(" Extracted %d common stocks from FinMind.\n\n", nrow(df_fm_clean)))
## Extracted 1226 common stocks from FinMind.
PART 3: Export
write.csv(df_twse_clean, "twse_common_stocks.csv", row.names = FALSE, fileEncoding = "UTF-8")
write.csv(df_fm_clean, "finmind_common_stocks.csv", row.names = FALSE, fileEncoding = "UTF-8")
cat("Done.\n")
## Done.