멀티프로세싱과 멀티스레딩을 결합해서 크롤링을 하는데 누락되는 데이터가 있습니다. 어떻게 해결할 수 있을까요?

조회수 1724회

multiprocessing, threading, beautifulsoup, requests 를 이용해서 크롤링을 하는데 list index out of range error 혹은 None type has not attribute text error가 납니다.

import requests
from bs4 import BeautifulSoup as BS
from multiprocessing import Process
from queue import Queue
from functools import partial
from threading import Lock, Thread, local
from glob import glob

def crawl(urls, q, fn):
    lock = Lock()
    for url in urls:
        # sleep(0.25)
        lock.acquire()
        spon = []
        date = []
        cosp = []

        req = requests.get(url)
        html = req.text
        soup = BS(html, "html.parser")

        table = soup.find("table", {"class" : "standard01"})
        tr = table.find_all("tr")

        for t in tr:
            if "Sen." in t.find("td").text:
                sd = t.find("td").text
                break
        # sp = soup.select('body table.standard01 > tr td')
        # sd = sp[0].text

        spon.append(sd[:sd.index("(")-1])
        date.append(sd[sd.index("/")-2:sd.index("/")+8])


        url = url.replace("?","/cosponsors?")
        req = requests.get(url)
        html = req.text
        soup = BS(html, "html.parser")

        cosps = soup.select('#main > ol > li.expanded > table > tbody > tr')
        temp = []
        for c in cosps:
            text = c.text.replace("\n","").replace("*","")
            text = text[:text.index("]")+1]
            temp.append(text)
        cosp.append(temp)

        q.put((spon, date, cosp))
        save(fn, q)
        # lock.release()

def save(fn, q):
    spon, date, cosp = q.get()
    f = open("./raw/"+fn + ".txt","a", encoding = "utf-8")
    for s in spon:
        f.write(s)
        f.write("\t")
        f.write(date[spon.index(s)])
        f.write("\t")
        for c in cosp[spon.index(s)]:
            f.write(c)
            f.write(", ")
        f.write("\n")
    f.close()

def do_thr(urls, func, fn):
    q = Queue()

    urls = [urls[i:i+len(urls)//5] for i in range(0,len(urls),len(urls)//5)]

    if len(urls) > 5:
        urls[4].extend(urls[5])
        del urls[5]

    th = []
    for i in range(len(urls)):
        t = Thread(target = crawl, args = (urls[i], q, fn, ))
        th.append(t)
        t.start()

    for t in th:
        t.join()


if __name__ == "__main__":

    files = glob("./links/*.txt")
    q = Queue()

    for file in files:
        print(file)
        fn = file[file.index("links\\")+6:file.index("_address.txt")]

        urls = open(file, "r", encoding = "utf-8").read().splitlines()
        urls = [urls[i:i+len(urls)//5] for i in range(0,len(urls), len(urls)//5)]

        if len(urls) > 5:
            urls[4].extend(urls[5])
            del urls[5]

        proc = []

        for i in range(len(urls)):
            p = Process(target = do_thr , args = (urls[i], crawl, fn, ))
            proc.append(p)
            p.start()

        for p in proc:
            p.join()

처음엔 beautifulsoup.select를 썼었는데 list index out of range error가 나서 find로 바꾸면 나을까 해서 find로 바꿨는데 None type error가 나네요..

전부 다 에러가 나는 것도 아니고 중간에 잘 가다가 에러가 납니다. 멀티스레딩이나 멀티프로세싱만 하면 또 에러가 안 나구요. 싱글 프로세스로만 돌려도 에러가 나지는 않습니다.

멀티 프로세싱이나 멀티스레딩으로만 하면 속도가 너무 느려서 같이 쓰고 싶은데 어떻게 해결할 수 있을까요.. find로 바꾼 후 에러가 나는 부분은 table.find_all("tr") 부분에서 납니다. select로 했을 때는 주석처리 되어 있는 sp[0].text 부분에서 list index out of range가 났구요.

try except를 써서 직접 내용물을 보면 select의 경우 빈 리스트가 출력되고, beautifulsoup은 None이 나오기는 하더라구요.

뭔가 멀티 스레딩과 멀티 프로세싱이 같이 돌아가는 과정에서 충돌이 나서 값이 비는 것 같은 느낌이기도 한데, 어떻게 고쳐야 할 지 모르겠습니다. 크롤링 해야 하는 링크의 일부입니다. 전체 개수는 3000개*27개 정도 되는데 속도가 너무 느려서 같이 써야 좀 속도가 나올 것 같아서요..

https://www.congress.gov/bill/98th-congress/senate-resolution/148?s=1&r=1 https://www.congress.gov/bill/98th-congress/senate-bill/2766?s=1&r=2 https://www.congress.gov/bill/98th-congress/senate-bill/2413?s=1&r=3 https://www.congress.gov/bill/98th-congress/senate-bill/137?s=1&r=4 https://www.congress.gov/bill/98th-congress/senate-resolution/132?s=1&r=5 https://www.congress.gov/bill/98th-congress/senate-concurrent-resolution/74?s=1&r=6

  • (•́ ✖ •̀)
    알 수 없는 사용자

1 답변

  • 알고 계시겠지만, 대부분의 사이트에서는 특정 ip에서 과도한 트래픽을 요청할 경우 이를 트래픽 공격으로 판단해 해당 ip를 밴 시킵니다.

    사이트에서 ip 밴을 시킨 경우 당연히 html 소스는 전송되지 않기 때문에 특정 태그를 select를 하던, find를 하던 해당 객체를 찾을 수가 없기 때문에 select는 빈 리스트를, find는 None을 반환합니다.

    또한 None 또는 아무것도 없는 개체는 text로 변환하면 에러를 출력하게 되죠.

    잘 되다가 갑자기 에러가 뜨는건 이런 문제로 보입니다.

    코어를 몇 개를 사용하는지는 모르겠으나, 최소 4개의 트래픽 요청이 지속적으로 요청되었기 때문에 사이트에서 이를 트래픽 공격으로 인식한 것으로 보입니다.

    이건 그냥 해당 에러가 나타났을 때, 해당 사이트를 브라우저로 방문해보면 사이트에 접속이 되는지 확인해보면 확인할 수 있습니다.

    이를 어떻게 해결하는가 하면.. 트래픽 요청 후에 time.sleep을 걸어서 잠시 대기 시간을 주거나, 멀티프로세싱을 사용하지 않고 1회 1요청을 하는 것 말고는 방법이 없습니다.

    또, 제가 알기로 멀티프로세싱 모듈을 다음과 같이 사용하는 것이 아닌 것으로 알고 있습니다.

    프로세스 코어를 먼저 지정해준 다음 개별 코어에 명령을 할당하는 명령어가 있어야 하는 것으로 하는데 해당 과정이 없는 것으로 보입니다.

    제가 알기로는 다음과 같이 작동할텐데, 확인 부탁드립니다.

            proc = []
    
            for i in range(len(urls)):
                p = Process(target = do_thr , args = (urls[i], crawl, fn, ))    # p 지정과 함께 do_thr 실행
                proc.append(p)
                p.start()    # p를 다시 한 번 실행
    
            for p in proc:
                p.join()
    
    • sleep을 걸었는데도 못 가져오던데 혹시 몇 초 정도 걸어주는게 좋을지 알 수 있을까요? 알 수 없는 사용자 2021.2.9 11:07
    • 그리고 멀티 프로세싱의 process 같은 경우는 Process는 프로세스 지정부분이고, p.start()를 해줘야 시작한다고 하더라구요. Pool같은 경우는 pool = Pool(프로세스 개수) pool.map(func, arg) 이렇게 하면 바로 시작되는데 process는 수동으로 시작해야 하더라구요. 답변 감사합니다. 알 수 없는 사용자 2021.2.9 11:09
    • 대기 시간은 사이트마다 달라서 반복작업으로 체크해봐야 합니다. 초보자 2021.2.9 11:19
    • 네 감사합니다 알 수 없는 사용자 2021.2.9 11:22

답변을 하려면 로그인이 필요합니다.

프로그래머스 커뮤니티는 개발자들을 위한 Q&A 서비스입니다. 로그인해야 답변을 작성하실 수 있습니다.

(ಠ_ಠ)
(ಠ‿ಠ)