๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
Lect & Tip/Python

ํŒŒ์ด์ฌ ์ธ์Šคํƒ€๊ทธ๋žจ ์‚ฌ์ง„ ๋‹ค์šด๋กœ๋“œ ์Šคํฌ๋ฆฝํŠธ ๋งŒ๋“ค๊ธฐ

by st๊ณต๊ฐ„ 2025. 4. 24.
๋ฐ˜์‘ํ˜•

ํŒŒ์ด์ฌ ์ธ์Šคํƒ€๊ทธ๋žจ ์‚ฌ์ง„ ๋‹ค์šด๋กœ๋“œ ์Šคํฌ๋ฆฝํŠธ ๋งŒ๋“ค๊ธฐ

์ธ์Šคํƒ€๊ทธ๋žจ์˜ ๊ตฌ์กฐ๊ฐ€ ์ž์ฃผ ๋ฐ”๋€Œ๊ณ  ๊ณต์‹ API๋ฅผ ์“ฐ๋ ค๋ฉด ์•ฑ ๋“ฑ๋ก ๋ฐ ํ† ํฐ ๋ฐœ๊ธ‰ ์ ˆ์ฐจ๊ฐ€ ํ•„์š”ํ•˜๊ธฐ ๋•Œ๋ฌธ์—, ๊ฐ€์žฅ ์•ˆ์ •์ ์œผ๋กœ ์‚ฌ์ง„์„ ๋‹ค์šด๋กœ๋“œํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์€ Instaloader ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ด ์Šคํฌ๋ฆฝํŠธ๋Š” ๊ณต๊ฐœ ๊ณ„์ •์€ ๋กœ๊ทธ์ธ ์—†์ด, ๋น„๊ณต๊ฐœ ๊ณ„์ •์€ ๋กœ๊ทธ์ธ ํ›„์— ์‚ฌ์ง„์„ ๋‹ค์šด๋กœ๋“œํ•  ์ˆ˜ ์žˆ๋„๋ก ์„ค๊ณ„๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

์•„๋ž˜ ์˜ˆ์ œ๋ฅผ ์ฐธ๊ณ ํ•˜์‹œ๊ณ , ํ•„์š”์— ๋”ฐ๋ผ ์˜ต์…˜์„ ์กฐ์ •ํ•˜์—ฌ ์‚ฌ์šฉํ•˜์„ธ์š”.

# ๋จผ์ € Instaloader ์„ค์น˜
pip install instaloader

ํŒŒ์ด์ฌ ์ธ์Šคํƒ€๊ทธ๋žจ ์‚ฌ์ง„ ๋‹ค์šด๋กœ๋“œ ์Šคํฌ๋ฆฝํŠธ ์†Œ์Šค์ฝ”๋“œ

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
instagram_downloader.py

Instaloader๋ฅผ ์ด์šฉํ•ด ์ธ์Šคํƒ€๊ทธ๋žจ ์‚ฌ์ง„์„ ์ž๋™์œผ๋กœ ๋‹ค์šด๋กœ๋“œํ•˜๋Š” ์Šคํฌ๋ฆฝํŠธ์ž…๋‹ˆ๋‹ค.

Usage:
    # ๊ณต๊ฐœ ๊ณ„์ • ๋‹ค์šด๋กœ๋“œ
    python instagram_downloader.py --target naver

    # ๋น„๊ณต๊ฐœ ๊ณ„์ • ๋‹ค์šด๋กœ๋“œ (๋กœ๊ทธ์ธ ํ•„์š”)
    python instagram_downloader.py --target your_private_account \
        --login --username your_id --password your_pw

    # ํŠน์ • ๊ฒŒ์‹œ๋ฌผ URL๋งŒ ๋‹ค์šด๋กœ๋“œ
    python instagram_downloader.py --post-url https://www.instagram.com/p/CG0UU3PnR3Z/
"""

import os
import argparse
import instaloader

def download_profile(loader, username, output_dir):
    """ํ”„๋กœํ•„ ์ „์ฒด ๋‹ค์šด๋กœ๋“œ"""
    profile = instaloader.Profile.from_username(loader.context, username)
    target_dir = os.path.join(output_dir, username)
    os.makedirs(target_dir, exist_ok=True)

    print(f"[+] '{username}' ํ”„๋กœํ•„์—์„œ ๊ฒŒ์‹œ๋ฌผ ๊ฐ€์ ธ์˜ค๋Š” ์ค‘...")
    for post in profile.get_posts():
        print(f"    → {post.shortcode}", end=" ")
        loader.download_post(post, target=target_dir)
        print("์™„๋ฃŒ")

def download_post(loader, post_url, output_dir):
    """๋‹จ์ผ ๊ฒŒ์‹œ๋ฌผ ๋‹ค์šด๋กœ๋“œ"""
    shortcode = post_url.rstrip("/").split("/")[-1]
    post = instaloader.Post.from_shortcode(loader.context, shortcode)
    target_dir = os.path.join(output_dir, shortcode)
    os.makedirs(target_dir, exist_ok=True)

    print(f"[+] ๊ฒŒ์‹œ๋ฌผ {shortcode} ๋‹ค์šด๋กœ๋“œ ์ค‘...")
    loader.download_post(post, target=target_dir)
    print("์™„๋ฃŒ")

def main():
    parser = argparse.ArgumentParser(
        description="Instaloader ๊ธฐ๋ฐ˜ ์ธ์Šคํƒ€๊ทธ๋žจ ์‚ฌ์ง„ ๋‹ค์šด๋กœ๋“œ ์Šคํฌ๋ฆฝํŠธ"
    )
    group = parser.add_mutually_exclusive_group(required=True)
    group.add_argument("--target",         help="์‚ฌ์šฉ์ž ๊ณ„์ •๋ช… (username)")
    group.add_argument("--post-url",       help="๋‹ค์šด๋กœ๋“œํ•  ๊ฒŒ์‹œ๋ฌผ URL")
    parser.add_argument("--login",         action="store_true", help="๋น„๊ณต๊ฐœ ๊ณ„์ • ๋‹ค์šด๋กœ๋“œ๋ฅผ ์œ„ํ•ด ๋กœ๊ทธ์ธ")
    parser.add_argument("--username",      help="์ธ์Šคํƒ€๊ทธ๋žจ ๋กœ๊ทธ์ธ ์•„์ด๋””")
    parser.add_argument("--password",      help="์ธ์Šคํƒ€๊ทธ๋žจ ๋กœ๊ทธ์ธ ๋น„๋ฐ€๋ฒˆํ˜ธ")
    parser.add_argument("--output-dir",    default="downloads", help="์ €์žฅ ํด๋” (๊ธฐ๋ณธ: downloads)")

    args = parser.parse_args()

    loader = instaloader.Instaloader(
        download_comments=False,
        save_metadata=False,
        post_metadata_txt_pattern="",  # ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ํ…์ŠคํŠธ ํŒŒ์ผ ๋ฏธ์ƒ์„ฑ
        dirname_pattern="{target}"     # ๋‹ค์šด๋กœ๋“œ ๋””๋ ‰ํ† ๋ฆฌ ํŒจํ„ด
    )

    # ๋กœ๊ทธ์ธ ์ฒ˜๋ฆฌ
    if args.login:
        if not (args.username and args.password):
            parser.error("๋กœ๊ทธ์ธ์„ ์‚ฌ์šฉํ•˜๋ ค๋ฉด --username ๊ณผ --password ๋ฅผ ๋ชจ๋‘ ์ง€์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.")
        print("[*] ๋กœ๊ทธ์ธ ์‹œ๋„ ์ค‘...")
        loader.login(args.username, args.password)
        print("[+] ๋กœ๊ทธ์ธ ์„ฑ๊ณต")

    os.makedirs(args.output_dir, exist_ok=True)
    os.chdir(args.output_dir)

    # ์‹คํ–‰ ๋ถ„๊ธฐ
    if args.post_url:
        download_post(loader, args.post_url, ".")
    else:
        download_profile(loader, args.target, ".")

if __name__ == "__main__":
    main()

์‚ฌ์šฉ๋ฐฉ๋ฒ• ์š”์•ฝ

  1. Instaloader ์„ค์น˜
    • bashpip install instaloader
    • ๋ณต์‚ฌํŽธ์ง‘
  2. ์Šคํฌ๋ฆฝํŠธ ์‹คํ–‰ ๊ถŒํ•œ ๋ถ€์—ฌ (Linux/Mac)
    • bashchmod +x instagram_downloader.py
    • ๋ณต์‚ฌํŽธ์ง‘
  3. ๊ณต๊ฐœ ๊ณ„์ • ์ „์ฒด ๋‹ค์šด๋กœ๋“œ
    • bashpython instagram_downloader.py --target instagram_username
    • ๋ณต์‚ฌํŽธ์ง‘
  4. ๋น„๊ณต๊ฐœ ๊ณ„์ • ๋‹ค์šด๋กœ๋“œ (๋กœ๊ทธ์ธ ํ•„์š”)
    • bashpython instagram_downloader.py --target private_username \ --login --username your_id --password your_pw
    • ๋ณต์‚ฌํŽธ์ง‘
  5. ํŠน์ • ๊ฒŒ์‹œ๋ฌผ(URL)๋งŒ ๋‹ค์šด๋กœ๋“œ

์ด ์Šคํฌ๋ฆฝํŠธ๋ฅผ ํ™œ์šฉํ•˜๋ฉด ๊ฐ„๋‹จํ•œ ๋ช…๋ น์–ด๋งŒ์œผ๋กœ ์›ํ•˜๋Š” ๊ณ„์ •์ด๋‚˜ ๊ฒŒ์‹œ๋ฌผ์˜ ์‚ฌ์ง„์„ ์†์‰ฝ๊ฒŒ ๋กœ์ปฌ์— ์ €์žฅํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ•„์š”์— ๋”ฐ๋ผ Instaloader์˜ ์ถ”๊ฐ€ ์˜ต์…˜(์˜ˆ: ๋™์˜์ƒ ํฌํ•จ, ์Šคํ† ๋ฆฌ ๋‹ค์šด๋กœ๋“œ ๋“ฑ)์„ ๊ณต์‹ ๋ฌธ์„œ์—์„œ ํ™•์ธํ•ด ์ ์šฉํ•ด ๋ณด์„ธ์š”.

o4-mini-high

๋‚˜์˜ ๋ง:

[+] ๊ฒŒ์‹œ๋ฌผ DIgK0tCxApJ ๋‹ค์šด๋กœ๋“œ ์ค‘...
python : Traceback (most recent call last):
์œ„์น˜ ์ค„:1 ๋ฌธ์ž:1
+ python instagram_downloader.py --post-url https://www.instagram.com/p ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (Traceback (most recent call last)::String) [], RemoteException
    + FullyQualifiedErrorId : NativeCommandError

  File "D:\WorkPY\InstaDownload\instagram_downloader.py", line 86, in <module>
    main()
    ~~~~^^
  File "D:\WorkPY\InstaDownload\instagram_downloader.py", line 81, in main
    download_post(loader, args.post_url, ".")
    ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "D:\WorkPY\InstaDownload\instagram_downloader.py", line 44, in download_post
    loader.download_post(post, target=target_dir)
    ~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\hidec\AppData\Local\Programs\Python\Python313\Lib\site-packages\instaloader\instaloader.py", line 745, in download_post
    downloaded &= self.download_pic(filename=sidecar_filename, url=sidecar_node.display_url,
                  ~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
                                    mtime=post.date_local, filename_suffix=suffix)
                                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\hidec\AppData\Local\Programs\Python\Python313\Lib\site-packages\instaloader\instaloader.py", line 97, in call
    return func(instaloader, *args, **kwargs)
  File "C:\Users\hidec\AppData\Local\Programs\Python\Python313\Lib\site-packages\instaloader\instaloader.py", line 356, in download_pic
    self.context.write_raw(resp, filename)
    ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^
  File "C:\Users\hidec\AppData\Local\Programs\Python\Python313\Lib\site-packages\instaloader\instaloadercontext.py", line 657, in write_raw
    self.log(filename, end=' ', flush=True)
    ~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\hidec\AppData\Local\Programs\Python\Python313\Lib\site-packages\instaloader\instaloadercontext.py", line 141, in log
    print(*msg, sep=sep, end=end, flush=flush)
    ~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
UnicodeEncodeError: 'cp949' codec can't encode character '\u2024' in position 0: illegal multibyte sequence

ChatGPT์˜ ๋ง:

5์ดˆ ๋™์•ˆ ์ƒ๊ฐํ•จ

์ธ์ฝ”๋”ฉ ๋ฌธ์ œ๋Š” Windows ์ฝ˜์†”์˜ ๊ธฐ๋ณธ cp949 ์ธ์ฝ”๋”ฉ์ด U+2024(โ€ค) ๊ฐ™์€ ์œ ๋‹ˆ์ฝ”๋“œ ๋ฌธ์ž๋ฅผ ์ฒ˜๋ฆฌํ•˜์ง€ ๋ชปํ•ด์„œ ๋ฐœ์ƒํ•˜๋Š” ์—๋Ÿฌ์ž…๋‹ˆ๋‹ค. ๊ฐ€์žฅ ๊ฐ„๋‹จํ•œ ํ•ด๊ฒฐ์ฑ…์€ ์Šคํฌ๋ฆฝํŠธ ์ตœ์ƒ๋‹จ์—์„œ stdout/stderr ์˜ ์ธ์ฝ”๋”ฉ์„ UTF-8 ๋กœ ์žฌ๊ตฌ์„ฑํ•ด ์ฃผ๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์•„๋ž˜ ํŒจ์น˜๋ฅผ ์ ์šฉํ•ด ๋ณด์„ธ์š”.

 #!/usr/bin/env python3
 # -*- coding: utf-8 -*-
 """
 instagram_downloader.py
@@
 import os
 import argparse
 import instaloader
+import sys

+### Windows ์ฝ˜์†”์—์„œ๋„ ์œ ๋‹ˆ์ฝ”๋“œ ๊นจ์ง ์—†์ด ์ถœ๋ ฅ๋˜๋„๋ก stdout/stderr ์ธ์ฝ”๋”ฉ ์žฌ๊ตฌ์„ฑ ###
+try:
+    # Python 3.7 ์ด์ƒ
+    sys.stdout.reconfigure(encoding="utf-8", errors="replace")
+    sys.stderr.reconfigure(encoding="utf-8", errors="replace")
+except AttributeError:
+    # ํ•˜์œ„ ํ˜ธํ™˜
+    import io
+    sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
+    sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding="utf-8", errors="replace")
+
 def download_profile(loader, username, output_dir):
     """ํ”„๋กœํ•„ ์ „์ฒด ๋‹ค์šด๋กœ๋“œ"""
     profile = instaloader.Profile.from_username(loader.context, username)

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด loader.context.log() ๋‚˜ ์ผ๋ฐ˜ print() ํ˜ธ์ถœ ์‹œ์—๋„ UTF-8๋กœ ์•ˆ์ „ํ•˜๊ฒŒ ์ถœ๋ ฅ๋˜์–ด ๋” ์ด์ƒ cp949 codec ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

–––

์ถ”๊ฐ€ ํŒ

ํŒŒ์ด์ฌ ์‹คํ–‰ ์‹œ -X utf8 ํ”Œ๋ž˜๊ทธ๋ฅผ ์ฃผ๋ฉด(python -X utf8 instagram_downloader.py …) ์ „์ฒด I/O์— UTF-8 ๋ชจ๋“œ๋ฅผ ๊ฐ•์ œ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

  • Windows ํ„ฐ๋ฏธ๋„(๋˜๋Š” PowerShell)์—์„œ chcp 65001 ๋ช…๋ น์œผ๋กœ ์ฝ˜์†” ์ธ์ฝ”๋”ฉ์„ UTF-8 ๋กœ ๋ณ€๊ฒฝํ•œ ๋’ค ์‹คํ–‰ํ•ด๋„ ๋™์ผํ•œ ํšจ๊ณผ๋ฅผ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋ฐ˜์‘ํ˜•

๋Œ“๊ธ€