๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
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 ๋กœ ๋ณ€๊ฒฝํ•œ ๋’ค ์‹คํ–‰ํ•ด๋„ ๋™์ผํ•œ ํšจ๊ณผ๋ฅผ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

    ๋ฐ˜์‘ํ˜•

    ๋Œ“๊ธ€