갈루아의 반서재

파이썬을 사용하여 이미지를 다운로드받는 웹 스크래핑을 구현해보자. 여기서 다룰 토픽들은 다음과 같다.

  • 정적 페이지 스크래핑
  • 동적 페이지 스크래핑
  • 구글 이미지 스크래핑
  • 법률상의 이슈

 

1. Scraping static pages (원격지 Ubuntu 18.04)

정적인 페이지를 스크래핑하는 것은 심플하다. 다음 위키디피아 페이지의 테이블을 가져오는 예를 보자.

필수 라이브러리인 requests 가 설치되어 있어야 한다. 해당 라이브러리가 설치되어 있지 않은 경우 다음과 같이 설치한다.

(AnnaM) founder@hilbert:~$ pip install requests==2.7.0
Collecting requests==2.7.0
  Downloading https://files.pythonhosted.org/packages/26/ff/c71b3943bebdd9f7ceb9e137296370587eb0b33fe2eb3732ae168bc45204/requests-2.7.0-py2.py3-none-any.whl (470kB)
     |████████████████████████████████| 471kB 2.8MB/s
Installing collected packages: requests
Successfully installed requests-2.7.0

그럼 설치 후 다음 코드를 실행해보자.

import requests

# download wikipage
wikipage = "https://en.wikipedia.org/wiki/List_of_sovereign_states_and_dependent_territories_by_continent_(data_file)"
result = requests.get(wikipage)
result.content

하지만 알아보기 어렵다. 이 경우 Beautiful Soup 이 도움이 된다. 해당 라이브러리를 설치하자.

(AnnaM) founder@hilbert:~$ conda install -c anaconda beautifulsoup4
Collecting package metadata: done
Solving environment: done


==> WARNING: A newer version of conda exists. <==
  current version: 4.6.4
  latest version: 4.7.12

Please update conda by running

    $ conda update -n base conda



## Package Plan ##

  environment location: /home/founder/anaconda3/envs/AnnaM

  added / updated specs:
    - beautifulsoup4


The following packages will be downloaded:

    package                    |            build
    ---------------------------|-----------------
    beautifulsoup4-4.8.0       |           py37_0         147 KB  anaconda
    ca-certificates-2019.8.28  |                0         132 KB  anaconda
    certifi-2019.9.11          |           py37_0         154 KB  anaconda
    soupsieve-1.9.3            |           py37_0          60 KB  anaconda
    ------------------------------------------------------------
                                           Total:         494 KB

The following NEW packages will be INSTALLED:

  beautifulsoup4     anaconda/linux-64::beautifulsoup4-4.8.0-py37_0
  soupsieve          anaconda/linux-64::soupsieve-1.9.3-py37_0

The following packages will be UPDATED:

  openssl              pkgs/main::openssl-1.1.1d-h7b6447c_1 --> anaconda::openssl-1.1.1-h7b6447c_0

The following packages will be SUPERSEDED by a higher-priority channel:

  ca-certificates                                 pkgs/main --> anaconda
  certifi                                         pkgs/main --> anaconda


Proceed ([y]/n)? y


Downloading and Extracting Packages
soupsieve-1.9.3      | 60 KB     | ###################################### | 100%
ca-certificates-2019 | 132 KB    | ###################################### | 100%
beautifulsoup4-4.8.0 | 147 KB    | ###################################### | 100%
certifi-2019.9.11    | 154 KB    | ###################################### | 100%
Preparing transaction: done
Verifying transaction: done
Executing transaction: done

Beautiful Soup을 이용하여 "table" 타입에 “wikitable sortable.” 클래스를 찾을 수 있다. 그리고나서 테이블을 반복하면서 한 줄 한 줄 데이터를 추출하게 된다. 결과는 아래와 같다.

import requests
import pandas as pd
from bs4 import BeautifulSoup

# download wikipage
wikipage = "https://en.wikipedia.org/wiki/List_of_sovereign_states_and_dependent_territories_by_continent_(data_file)"
result = requests.get(wikipage)

# if successful parse the download into a BeautifulSoup object, which allows easy manipulation 
if result.status_code == 200:
    soup = BeautifulSoup(result.content, "html.parser")
    
# find the object with HTML class wibitable sortable
table = soup.find('table',{'class':'wikitable sortable'})

# loop through all the rows and pull the text
new_table = []
for row in table.find_all('tr')[1:]:
    column_marker = 0
    columns = row.find_all('td')
    new_table.append([column.get_text() for column in columns])
    
df = pd.DataFrame(new_table, columns=['ContinentCode','Alpha2','Alpha3','PhoneCode','Name'])
df['Name'] = df['Name'].str.replace('\n','')
df

pandas 가 설치되어 있지 않다면 다음과 같이 설치한다. 

(AnnaM) founder@hilbert:~$ conda install -c anaconda pandas

 

2. Scraping interactive pages (로컬 윈도우 10)

하지만 요즘 대부분의 웹페이지는 동적 페이지이다. 동적 페이지의 경우 Selenium 을 사용하여 스크래핑이 가능하다. 통상 Selenium 은 웹브라우저 액션을 자동화하기 위한 도구로, 실제 사용자처럼 브라우저를 열어, 커서를 버튼 근처로 움직인 다음 클릭을 한다. 마찬가지로 지금 다루고 있는 이미지 다운로드와 같이 Selenium 은 반복되는 웹 태스크에도 탁월한 성능을 발휘한다. 그럼 구글 크롬을 이용하여 Selenium 을 설치하고 사용하는 방법에 대해 알아보자.

  1. 구글 크롬을 설치한다
  2. 크롬 버전을 확인한다. 필자의 경우 77.0.3865.90 으로 메인버전은 77이다.
  3. 메인 버전 77에 맞는 ChromeDriver 를 여기서 다운로드하여 설치한다.
  4. pip install selenium 명령을 통해 Python Selenium 패키지를 설치한다.

* 크롬 버전 확인

* 크롬드라이버 다운로드

Current Releases

If you are using Chrome from Dev or Canary channel, please following instructions on the ChromeDriver Canary page.

 

For more information on selecting the right version of ChromeDriver, please see the Version Selection page.

* Python Selenium 패키지 설치

(AnnaM) founder@hilbert:~$ pip install selenium
Collecting selenium
  Downloading https://files.pythonhosted.org/packages/80/d6/4294f0b4bce4de0abf13e17190289f9d0613b0a44e5dd6a7f5ca98459853/selenium-3.141.0-py2.py3-none-any.whl (904kB)
     |████████████████████████████████| 911kB 2.8MB/s
Collecting urllib3 (from selenium)
  Downloading https://files.pythonhosted.org/packages/e0/da/55f51ea951e1b7c63a579c09dd7db825bb730ec1fe9c0180fc77bfb31448/urllib3-1.25.6-py2.py3-none-any.whl (125kB)
     |████████████████████████████████| 133kB 33.1MB/s
Installing collected packages: urllib3, selenium
Successfully installed selenium-3.141.0 urllib3-1.25.6

이제 WebDriver 구동을 합니다. 본인의 크롬 드라이버의 패스를 입력하여 다음의 스니펫을 실행합니다.

import selenium
from selenium import webdriver

# This is the path I use
# DRIVER_PATH = '.../Desktop/Scraping/chromedriver 2'
# Put the path for your ChromeDriver here
DRIVER_PATH = "C:\\Users\\alicia\\Downloads\\chromedriver"
wd = webdriver.Chrome(executable_path=DRIVER_PATH)

접근허용을 해주면, 

다음과 같은 화면을 보실 수 있습니다.

노트북에 새로운 셀을 추가하여 다음의 명령을 실행합니다.

wd.get('https://google.com') 

구글 페이지로 자동 이동한 것을 보실 수 있습니다.


그리고 셀을 추가하여 다음의 코드를 실행하면, Dogs 를 자동 검색하는 것을 확인할 수 있다. 

search_box = wd.find_element_by_css_selector('input.gLFyf') 
search_box.send_keys('Dogs') 

* 윈도우 10에 파이썬을 설치하고 주피터랩을 구동하는 부분은 다음 포스팅을 참고하세요.

http://egloos.zum.com/mcchae/v/11302331

 

[파이썬] 윈도우10에서 jupyter lab 이용하기

ipython 이라는 것은 파이썬의 확장 쉘로서 2001년 부터 시작되었습니다. [1], [2].. 등과 같이 명령 히스토리도 지원하고, 컨텍스트 센서티브 헬프도 지원합니다. 그 이후 Notebook 이라는 것도 붙어 웹으로 상호 소통을 하면서 프로그램을 해 볼 수 있는 것이 나왔는데 이를 ipython notebook 이라고 합니다. 이것이 다시 20

egloos.zum.com

 

3. Scraping images from Google (로컬 윈도우 10)

이제 기본은 이해를 하였으니 특정 키워드를 검색하여 이에 해당하는 이미지 링크를 가져와 다운로드하는 방법을 알아본다. 위의 노트북 파일에 새로운 셀을 추가하여 계속 진행한다.

특정 문장을 검색하여 해당 이미지 링크 가져오기

def fetch_image_urls(query:str, max_links_to_fetch:int, wd:webdriver, sleep_between_interactions:int=1):
    def scroll_to_end(wd):
        wd.execute_script("window.scrollTo(0, document.body.scrollHeight);")
        time.sleep(sleep_between_interactions)    
    
    # build the google query
    search_url = "https://www.google.com/search?safe=off&site=&tbm=isch&source=hp&q={q}&oq={q}&gs_l=img"

    # load the page
    wd.get(search_url.format(q=query))

    image_urls = set()
    image_count = 0
    results_start = 0
    while image_count < max_links_to_fetch:
        scroll_to_end(wd)

        # get all image thumbnail results
        thumbnail_results = wd.find_elements_by_css_selector("img.rg_ic")
        number_results = len(thumbnail_results)
        
        print(f"Found: {number_results} search results. Extracting links from {results_start}:{number_results}")
        
        for img in thumbnail_results[results_start:number_results]:
            # try to click every thumbnail such that we can get the real image behind it
            try:
                img.click()
                time.sleep(sleep_between_interactions)
            except Exception:
                continue

            # extract image urls    
            actual_images = wd.find_elements_by_css_selector('img.irc_mi')
            for actual_image in actual_images:
                if actual_image.get_attribute('src'):
                    image_urls.add(actual_image.get_attribute('src'))

            image_count = len(image_urls)

            if len(image_urls) >= max_links_to_fetch:
                print(f"Found: {len(image_urls)} image links, done!")
                break
        else:
            print("Found:", len(image_urls), "image links, looking for more ...")
            time.sleep(1)
            load_more_button = wd.find_element_by_css_selector(".ksb")
            if load_more_button:
                wd.execute_script("document.querySelector('.ksb').click();")

        # move the result startpoint further down
        results_start = len(thumbnail_results)

    return image_urls

fetch_image_urls 함수는 다음의 3가지 파라메터를 입력으로 요구한다.

  • query : Dog 와 같은 검색어 
  • max_links_to_fetch : 스크랩 대상 링크 갯수 
  • webdriver : 웹드라이버 인스턴스

이미지 다운로드
먼저 다음의 스니펫이 작동하도록 하기 위해서는 PIL 을 설치해야 한다. 

def persist_image(folder_path:str,url:str):
    try:
        image_content = requests.get(url).content

    except Exception as e:
        print(f"ERROR - Could not download {url} - {e}")

    try:
        image_file = io.BytesIO(image_content)
        image = Image.open(image_file).convert('RGB')
        file_path = os.path.join(folder_path,hashlib.sha1(image_content).hexdigest()[:10] + '.jpg')
        with open(file_path, 'wb') as f:
            image.save(f, "JPEG", quality=85)
        print(f"SUCCESS - saved {url} - as {file_path}")
    except Exception as e:
        print(f"ERROR - Could not save {url} - {e}")

persist_image 함수는 이미지 URL 을 찾아 folder_path 에 다운로드한다. 그리고 10자리의 랜덤한 파일명을 부여하는 역할을 한다. 

다음 함수인 search_and_download 는 앞선 2가지 함수를 합쳐서 여기에 ChromeDriver 의 탄력적이 사용이 가능하도록 처리했다. ChromeDriver 사용시 브라우저가 정상적으로 종료되도록 with 구문을 사용하는데, 그리고 search_and_download 는 다운로드받을 이미지의 수를 정하는데 기본값은 5이다.

def search_and_download(search_term:str,driver_path:str,target_path='./images',number_images=50):
    target_folder = os.path.join(target_path,'_'.join(search_term.lower().split(' ')))

    if not os.path.exists(target_folder):
        os.makedirs(target_folder)

    with webdriver.Chrome(executable_path=driver_path) as wd:
        res = fetch_image_urls(search_term, number_images, wd=wd, sleep_between_interactions=0.5)
        
    for elem in res:
        persist_image(target_folder,elem)

이제 다음 코드를 실행하면 된다. tensorflow 관련 이미지를 다운로드 받아보자.

search_term = 'tensorflow'

search_and_download(
    search_term = search_term,
    driver_path = DRIVER_PATH
)

필요한 라이브러리가 없다는 에러 메시지가 다수 뜰 수 있으니, 해당 라이브러리를 설치하고 가져오는 것을 잊어서는 안된다. 

Found: 100 search results. Extracting links from 0:100
Found: 5 image links, done!
ERROR - Could not save https://www.tensorflow.org/site-assets/images/marketing/cards/tile_oreilly.jpg?hl=ko - name 'hashlib' is not defined
ERROR - Could not save https://www.tensorflow.org/images/tf_logo_social.png - name 'hashlib' is not defined
ERROR - Could not save https://upload.wikimedia.org/wikipedia/commons/thumb/1/11/TensorFlowLogo.svg/1200px-TensorFlowLogo.svg.png - name 'hashlib' is not defined
ERROR - Could not save https://yt3.ggpht.com/a/AGF-l7_FycTwXc9yWHX21yMHs9tkuHtt1033X5HowA=s900-c-k-c0xffffffff-no-rj-mo - name 'hashlib' is not defined
ERROR - Could not save https://camo.githubusercontent.com/9631e43abb49a73960dad1669767ce02666a69a9/68747470733a2f2f7261772e6769746875622e636f6d2f74656e736f72666c6f772f7466782f6d61737465722f646f63732f67756964652f646961675f616c6c2e7376673f73616e6974697a653d74727565 - cannot identify image file <_io.BytesIO object at 0x0000000005F91BE8>
Found: 0 search results. Extracting links from 0:0
Found: 0 image links, looking for more ...
---------------------------------------------------------------------------
NoSuchElementException                    Traceback (most recent call last)
<ipython-input-42-696415c04d97> in <module>
      3 search_and_download(
      4     search_term = search_term,
----> 5     driver_path = DRIVER_PATH
      6 )

<ipython-input-41-9bec3e5425b1> in search_and_download(search_term, driver_path, target_path, number_images)
      6 
      7     with webdriver.Chrome(executable_path=driver_path) as wd:
----> 8         res = fetch_image_urls(search_term, number_images, wd=wd, sleep_between_interactions=0.5)
      9 
     10     for elem in res:

<ipython-input-39-1cf959320862> in fetch_image_urls(query, max_links_to_fetch, wd, sleep_between_interactions)
     51             print("Found:", len(image_urls), "image links, looking for more ...")
     52             time.sleep(1)
---> 53             load_more_button = wd.find_element_by_css_selector(".ksb")
     54             if load_more_button:
     55                 wd.execute_script("document.querySelector('.ksb').click();")

c:\users\alicia\jupyterlab\lib\site-packages\selenium\webdriver\remote\webdriver.py in find_element_by_css_selector(self, css_selector)
    596             element = driver.find_element_by_css_selector('#foo')
    597         """
--> 598         return self.find_element(by=By.CSS_SELECTOR, value=css_selector)
    599 
    600     def find_elements_by_css_selector(self, css_selector):

c:\users\alicia\jupyterlab\lib\site-packages\selenium\webdriver\remote\webdriver.py in find_element(self, by, value)
    976         return self.execute(Command.FIND_ELEMENT, {
    977             'using': by,
--> 978             'value': value})['value']
    979 
    980     def find_elements(self, by=By.ID, value=None):

c:\users\alicia\jupyterlab\lib\site-packages\selenium\webdriver\remote\webdriver.py in execute(self, driver_command, params)
    319         response = self.command_executor.execute(driver_command, params)
    320         if response:
--> 321             self.error_handler.check_response(response)
    322             response['value'] = self._unwrap_value(
    323                 response.get('value', None))

c:\users\alicia\jupyterlab\lib\site-packages\selenium\webdriver\remote\errorhandler.py in check_response(self, response)
    240                 alert_text = value['alert'].get('text')
    241             raise exception_class(message, screen, stacktrace, alert_text)
--> 242         raise exception_class(message, screen, stacktrace)
    243 
    244     def _value_or_default(self, obj, key, default):

NoSuchElementException: Message: no such element: Unable to locate element: {"method":"css selector","selector":".ksb"}
  (Session info: chrome=77.0.3865.90)

https://stackoverflow.com/questions/57186504/unable-to-locate-element-methodcss-selector-selectorid-gender1

 

Unable to locate element: {"method":"css selector","selector":"#id_gender1"}

I'm trying to select a checkbox / radio button by all the possible ways that I can think of, but none of them have worked. This is the way I am trying to identity de checkbox WebElement selectGen...

stackoverflow.com

위와 같은 에러가 발생하는 경우 fetch_image_urls 함수내의 time.sleep 시간을 넉넉히 부여한 뒤 진행해본다.

다시 실행해보자.

Found: 200 search results. Extracting links from 0:200
Found: 5 image links, done!
SUCCESS - saved https://www.tensorflow.org/site-assets/images/marketing/cards/tile_oreilly.jpg?hl=ko - as ./images\tensorflow\45f19c0f2a.jpg
SUCCESS - saved https://www.tensorflow.org/images/tf_logo_social.png - as ./images\tensorflow\5ddfd10286.jpg
SUCCESS - saved https://upload.wikimedia.org/wikipedia/commons/thumb/1/11/TensorFlowLogo.svg/1200px-TensorFlowLogo.svg.png - as ./images\tensorflow\3783a1fcba.jpg
SUCCESS - saved https://yt3.ggpht.com/a/AGF-l7_FycTwXc9yWHX21yMHs9tkuHtt1033X5HowA=s900-c-k-c0xffffffff-no-rj-mo - as ./images\tensorflow\8e410cfaf0.jpg
ERROR - Could not save https://camo.githubusercontent.com/9631e43abb49a73960dad1669767ce02666a69a9/68747470733a2f2f7261772e6769746875622e636f6d2f74656e736f72666c6f772f7466782f6d61737465722f646f63732f67756964652f646961675f616c6c2e7376673f73616e6974697a653d74727565 - cannot identify image file <_io.BytesIO object at 0x0000000005174588>

4개의 이미지가 다운로드된 것을 확인할 수 있다.

이미지의 퀄리티는 다음과 같이 수정할 수 있다.

앞서 살펴봤지만 다운로드 받을 이미지의 수, 경로 등 수정가능하다. 

한글 검색도 물론 잘된다. 레이싱모델 송주아로 검색했다.

search_term = '송주아'

search_and_download(
    search_term = search_term,
    driver_path = DRIVER_PATH
)
Found: 200 search results. Extracting links from 0:200
Found: 50 image links, done!
SUCCESS - saved https://i.pinimg.com/originals/44/77/b4/4477b49858a7fd6ec3969f59c641b5c4.gif - as ./images\송주아\442e72d0d0.jpg
SUCCESS - saved http://mblogthumb3.phinf.naver.net/MjAxNzA3MTdfMjI3/MDAxNTAwMjIwMTEyNDM2.nQQzgrjFsiYdNSoLrZSbxDbmkVvQiNcVqPag5HQYtu4g.5x_BVAHi47lISUciCizkppR75KvStr-ZB4c70gxHADAg.JPEG.ramantic/RAM07992-p.jpg?type=w800 - as ./images\송주아\e9d2d1b525.jpg
SUCCESS - saved https://pbs.twimg.com/media/D36rVzrUIAAnJyd.jpg - as ./images\송주아\a3ad13a801.jpg
SUCCESS - saved http://www.newsfreezone.co.kr/news/photo/201803/44431_41145_3529.jpg - as ./images\송주아\7d72e56be6.jpg
SUCCESS - saved http://optimal.inven.co.kr/upload/2019/05/26/bbs/i14096448762.jpg - as ./images\송주아\dffa320c01.jpg
SUCCESS - saved http://www.artkoreatv.com/news/photo/201702/img_35892_106979_49490.jpg - as ./images\송주아\7c2ce6c9a4.jpg
SUCCESS - saved http://www.top-rider.com/news/photo/201504/20092_51450_2820.JPG - as ./images\송주아\2cf2e2b8d0.jpg
SUCCESS - saved http://postfiles9.naver.net/MjAxOTA0MDVfMTc2/MDAxNTU0NDY4NTY0NTEw.p8pw73s45vNXMm4lC_6nEV1hxUjYeS_CIzXlN3hJdMQg.690s5Lo1rB6gbZsCo3083h-f0cWtOAy6penDF2A-O1sg.PNG.sniper1992/2019_04_01_%EC%84%9C%EC%9A%B8%EB%AA%A8%ED%84%B0%EC%87%BC_%EC%86%A1%EC%A3%BC%EC%95%84_10.png?type=w966 - as ./images\송주아\0cf591f361.jpg
SUCCESS - saved http://image.sportsseoul.com/2019/03/09/news/2019030901000347700025811.jpg - as ./images\송주아\7327012415.jpg
SUCCESS - saved https://scontent-lhr3-1.cdninstagram.com/v/t51.2885-15/e35/67024113_403251330534421_40096619772291006_n.jpg?_nc_ht=scontent-lhr3-1.cdninstagram.com&se=7&oh=a1aa95e70efaced6d501c217fcb4a5f7&oe=5DF72A74&ig_cache_key=MjA5MTMzMjg4NTMzNjY0NzQ1Mg%3D%3D.2 - as ./images\송주아\09286c11f6.jpg
SUCCESS - saved http://cphoto.asiae.co.kr/listimglink/1/2019020723102427964_1549548624.jpg - as ./images\송주아\beb743f773.jpg
SUCCESS - saved https://img.seoul.co.kr/img/upload/2019/03/10/SSI_20190310155539_V.jpg - as ./images\송주아\008dc14ca8.jpg
SUCCESS - saved http://postfiles7.naver.net/MjAxOTA0MDVfMjU1/MDAxNTU0NDY4NTYyNDEy.ZTNrnVnY52thKAWNKLw1iKUD-rPcuzSc9Wiv-CwN-cMg.kdmbWsPTgmykdwwVmRwqq_dLEfa_ngYv_TElSpUbjUMg.PNG.sniper1992/2019_04_01_%EC%84%9C%EC%9A%B8%EB%AA%A8%ED%84%B0%EC%87%BC_%EC%86%A1%EC%A3%BC%EC%95%84_06.png?type=w966 - as ./images\송주아\b31af7ca4b.jpg
SUCCESS - saved http://www.sportsw.kr/news/data/20190403/p1065579603821214_119_thum.JPG - as ./images\송주아\2e58fb9eeb.jpg
SUCCESS - saved https://i.ytimg.com/vi/PX2MWYxMoGI/maxresdefault.jpg - as ./images\송주아\11a3b2da9e.jpg
SUCCESS - saved http://www.newsfreezone.co.kr/news/photo/201903/103101_89438_3630.jpg - as ./images\송주아\a91fe3673c.jpg
ERROR - Could not save http://simg.donga.com/ugc/MLBPARK/Board/15/61/63/07/1561630753037.jpg - cannot identify image file <_io.BytesIO object at 0x00000000062A8108>
SUCCESS - saved https://3.bp.blogspot.com/-ksYakG4tjr8/XH28INDjRbI/AAAAAAAAAz0/JVb_Q61jQWsZkHzfGm8SjvhdYt94vvx8gCLcBGAs/s1600/_76A3910%2B%25EB%25B3%25B5%25EC%2582%25AC%2B6.jpg - as ./images\송주아\4d4989fc32.jpg
SUCCESS - saved http://postfiles7.naver.net/MjAxOTA0MDVfMTg2/MDAxNTU0NDY4NTYyMTMx.yN8EG--pjGZDpFqGQ9csRta4pjM_Hd7eCoR0A3L0Gcwg.nyBsq_pmXPOaex5MZtRrGz6eStWhguLvP3DsC69pwyUg.PNG.sniper1992/2019_04_01_%EC%84%9C%EC%9A%B8%EB%AA%A8%ED%84%B0%EC%87%BC_%EC%86%A1%EC%A3%BC%EC%95%84_01.png?type=w966 - as ./images\송주아\d524060638.jpg
SUCCESS - saved https://t1.daumcdn.net/cfile/tistory/99CE17405AE1E1FF28 - as ./images\송주아\91871f158f.jpg

다음과 같이 자동으로 다운로드가 진행된다. 

마지막 권고사항은 이러한 웹스크랩핑 자체가 불법은 아니지만, 해당 사이트의 이용 약관 등에 위배되지 않도록 이 부분을 반드시 살펴보고 진행해야 한다는 점이다. 

 

Image Scraping with Python

A code-along guide to learn how to download images from Google with Python!

towardsdatascience.com