Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions logger.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import logging
import time


def get_logger() -> logging.Logger:
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
Expand All @@ -14,9 +15,13 @@ def get_logger() -> logging.Logger:
stream_handler = logging.StreamHandler()
stream_handler.setLevel(logging.DEBUG)

file_handler = logging.FileHandler(os.path.join(log_path, f"{time.strftime('%Y-%m-%d_%H')}.log"))
file_handler = logging.FileHandler(
os.path.join(log_path, f"{time.strftime('%Y-%m-%d_%H')}.log")
)
file_handler.setLevel(logging.DEBUG)
file_handler.setFormatter(logging.Formatter("%(asctime)s - %(levelname)s - %(message)s"))
file_handler.setFormatter(
logging.Formatter("%(asctime)s - %(levelname)s - %(message)s")
)

logger.addHandler(stream_handler)
logger.addHandler(file_handler)
Expand Down
16 changes: 10 additions & 6 deletions run.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,25 +29,29 @@ def create_parser() -> argparse.ArgumentParser:
description="KBO Data Scraping Tool",
formatter_class=argparse.RawTextHelpFormatter,
)
subparsers = parser.add_subparsers(dest="command", help="Choose a scraping target", required=True)
subparsers = parser.add_subparsers(
dest="command", help="Choose a scraping target", required=True
)

# Common format argument function
def add_format_argument(parser: argparse.ArgumentParser) -> None:
parser.add_argument("-y", "--year", type=int, help="Season year (e.g., 2011)")
parser.add_argument("-d", "--date", type=str, help="Specify date (YYYYMMDD)")
parser.add_argument(
"-f", "--format",
"-f",
"--format",
type=str,
choices=["parquet", "json", "csv"],
default="csv",
help="Output format: 'parquet', 'json', or 'csv' (default: csv)."
help="Output format: 'parquet', 'json', or 'csv' (default: csv).",
)
parser.add_argument(
"-s", "--series",
"-s",
"--series",
type=int,
choices=[0,1,3,4,5,7,8,9],
choices=[0, 1, 3, 4, 5, 7, 8, 9],
default=0,
help="Series ID (default: 0) - 0: Regular Season, 1: Preseason, 3: Semi-PO, 4: Wildcard, 5: Playoff, 7: Korean Series, 8: International, 9: All-Star)"
help="Series ID (default: 0) - 0: Regular Season, 1: Preseason, 3: Semi-PO, 4: Wildcard, 5: Playoff, 7: Korean Series, 8: International, 9: All-Star)",
)

# Schedule data
Expand Down
15 changes: 10 additions & 5 deletions scrapers/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@

from logger import get_logger


class KBOBaseScraper(ABC):
"""Base class for scraping KBO data (player, game, schedule, team)."""

def __init__(self, format: str, series: list[int]):
def __init__(self, format: str, series: list[int]):
self.logger = get_logger()
self.start_year = 1982
self.current_year = datetime.now().year
Expand All @@ -21,7 +22,7 @@ def __init__(self, format: str, series: list[int]):
self.save_path = os.path.join(base_dir, "output", "processed")

self.format = format if format else "parquet"
self.series = series if series else [0,1,3,4,5,7,8,9]
self.series = series if series else [0, 1, 3, 4, 5, 7, 8, 9]

@abstractmethod
def _parse(self, response) -> tuple[list, list]:
Expand Down Expand Up @@ -84,7 +85,7 @@ def save(self, data: list, file_path: str):
"""
if not isinstance(data, list):
raise ValueError("Data must be a dictionary or a list of dictionaries.")

try:
full_path = os.path.join(self.save_path, f"{file_path}.{self.format}")
os.makedirs(os.path.dirname(full_path), exist_ok=True)
Expand All @@ -97,7 +98,9 @@ def save(self, data: list, file_path: str):
if self.format == "parquet":
df.to_parquet(full_path, engine="pyarrow", index=False)
elif self.format == "json":
json_str = df.to_json(orient="records", indent=4, force_ascii=False).replace(r"\/", "/")
json_str = df.to_json(
orient="records", indent=4, force_ascii=False
).replace(r"\/", "/")
with open(full_path, "w", encoding="utf-8") as f:
f.write(json_str)
elif self.format == "csv":
Expand Down Expand Up @@ -133,4 +136,6 @@ def run(self, year: int, date: str = None):
break

end_time = time.time()
self.logger.info(f"Scraping completed in {(end_time - start_time):.2f} seconds.")
self.logger.info(
f"Scraping completed in {(end_time - start_time):.2f} seconds."
)
41 changes: 20 additions & 21 deletions scrapers/game.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,25 +11,26 @@ def __init__(self, format, series):
super().__init__(format, series)

self.url = "https://www.koreabaseball.com/ws/Main.asmx/GetKboGameList"
self.payload = {
"leId": "1",
"srId": ",".join(map(str, self.series))
}
self.payload = {"leId": "1", "srId": ",".join(map(str, self.series))}

def _parse(self, response):
games = response.get("game", [])
if not games:
self.logger.warning("No game entries found in response.")
return None, None

headers = [key.strip() for key in games[0].keys()]
rows = [[value for value in game.values()] for game in games]

return headers, rows

def fetch(self, season, date):
start_date = datetime.strptime(date, "%Y%m%d") if date else datetime(season, 1, 1)
end_date = datetime.strptime(date, "%Y%m%d") if date else datetime(season, 12, 31)
start_date = (
datetime.strptime(date, "%Y%m%d") if date else datetime(season, 1, 1)
)
end_date = (
datetime.strptime(date, "%Y%m%d") if date else datetime(season, 12, 31)
)

result = {}
while start_date <= end_date:
Expand All @@ -48,7 +49,7 @@ def fetch(self, season, date):
if not rows:
self.logger.info(f"No rows returned for date {date_str}.")
continue

file_path = f"game/schedule/{season}/{date_str}"
self.backup(response, file_path, "json")

Expand All @@ -66,11 +67,9 @@ def fetch(self, season, date):
class GameResultScraper(KBOBaseScraper):
def __init__(self, format, series):
super().__init__(format, series)

self.url = "https://www.koreabaseball.com/ws/Schedule.asmx/GetScoreBoardScroll"
self.payload = {
"leId": "1"
}
self.payload = {"leId": "1"}

self.games = GameScheduleScraper(format, series)

Expand All @@ -79,8 +78,8 @@ def _parse(self, response):
if not maxInnings:
self.logger.warning("No inning datas found in response.")
return None, None
headers, rows = ["IS_HOME"], [[False],[True]]

headers, rows = ["IS_HOME"], [[False], [True]]
for key, value in response.items():
if key == "maxInning":
break
Expand All @@ -93,14 +92,14 @@ def _parse(self, response):

tables = [
json.loads(response.get("table2", "[]")),
json.loads(response.get("table3", "[]"))
json.loads(response.get("table3", "[]")),
]
for table in tables:
rows[0].extend([cell["Text"] for cell in table["rows"][0]["row"]])
rows[1].extend([cell["Text"] for cell in table["rows"][1]["row"]])

return headers, rows

def fetch(self, season, date):
result = {}

Expand Down Expand Up @@ -128,13 +127,13 @@ def fetch(self, season, date):

file_path = f"game/result/{season}/{game_id[:8]}"
self.backup(response, file_path, "json")

for row in rows:
result.setdefault(file_path, [])
result[file_path].append(convert_row_data(headers, row))
except Exception as e:
self.logger.error(f"Error fetching result for game id {game_id}: {e}")
self.logger.error(
f"Error fetching result for game id {game_id}: {e}"
)

return result


Loading