Python 크롤러의 실제 사례 - 특정 소설 크롤링

머리말

요즘에는 소설을 읽는 것을 좋아하는 친구들이 점점 더 많아지고 있지만, 소설을 읽기 위해 매번 특정 웹사이트에 접속하는 것은 매우 번거로운 일입니다. 특히 가끔 시골에 있는 고향에 돌아가면 신호가 약하고 데이터망을 사용할 수 없는 경우가 있는데 WIFI는 있나요? 그러니까 소설을 미리 다운받아도 괜찮을 것 같아요.

준비하다

사실 준비할 게 하나도 없어요. 크롤러가 자주 사용하는 모듈은 괜찮습니다. 현재는 요청, json 및 re라는 세 가지 모듈만 사용됩니다.

주제 시작

패킷 캡처

실제로 크롤러의 단계는 매우 고정되어 있습니다. 첫 번째 단계는 패키지를 잡고 무작위로 소설을 선택한 다음 무료 평가판을 클릭하는 것입니다. 그러면 소설의 내용이 우리에게 제공됩니다. 디렉토리를 클릭하고 주소 표시줄의 주소가 변경되지 않았으므로 Ajax에서 요청하고 Fetch/XHR에서 직접 데이터 패킷을 찾습니다.

 예상대로 모든 장의 json 데이터는 다음 데이터 패키지에서 발견되었습니다.

레이어별로 확장하고 각 색인 아래에서 이러한 콘텐츠를 찾습니다.

 

다음 단계는 이들 데이터와 각 챕터 사이의 관계를 찾는 것인데, 3개의 챕터의 주소 표시줄을 관찰한 결과 이전에 만든 뮤직 크롤러와 완전히 동일한 패턴을 쉽게 찾을 수 있었습니다.

2장

세 번째 장

4장 

 이 주소 앞의 모든 문자가 변경되지 않았으며 변경된 것은 마지막 데이터뿐이라는 것을 누구나 쉽게 알 수 있습니다. 이 데이터는 잘 관찰해 보면 위 데이터 패킷의 Chapter Index 아래 id에 해당하는 값임을 쉽게 알 수 있다. 그래서 이런 식으로 각 장의 주소가 해결됩니다.

다음으로, 이 소설의 텍스트 내용을 얻을 수 있는 방법을 찾기 위해 각 장의 세부 정보 페이지로 이동해야 합니다.

"all"의 첫 번째 패킷에서 소설의 텍스트 내용을 찾았습니다. 

 이쯤 되면 우리는 멈춰 서서 되돌아볼 수 있다. 지금까지 어떤 유용한 정보를 얻었나요?

1. 각 장의 주소는 URL입니다.

2. 각 챕터 세부정보 페이지의 소설 텍스트 내용은 어디에 있나요?

다음으로 우리가 해야 할 일은 이 URL을 기반으로 새로운 텍스트 콘텐츠를 얻는 것입니다.

유형 코드

먼저 목차 페이지의 데이터 패킷을 요청하고, 목차 페이지의 데이터 패킷을 통해 각 장의 URL을 획득한다. 여기서 주의할 점은 본 웹사이트는 어느 정도의 크롤링 방지 기능을 거쳤기 때문에 요청 헤더를 가져오지 않으면 데이터를 요청할 수 없습니다.

 

요청하는 URL과 요청 헤더는 위 그림과 같으므로 데이터를 요청할 수 있습니다. bookId에 주의해주세요. 사용하실 경우 이 bookId를 직접 입력하시면 정상적으로 작동됩니다. 방금 보여드린 각 장 URL의 끝에서 두 번째 데이터, 즉 끝에서 두 번째 위치에 있는 숫자열, 소설이에요. ID. 또 다른 점은 요청 헤더에 실제로 많은 암호화된 필드가 있다는 점인데, 이러한 필드를 추가하면 데이터가 요청되지 않으므로 스크린샷을 찍지 않았습니다. 요청 코드는 다음과 같습니다.

bookId = input('bookId:')
url = 'https://www.qidian.com/ajax/book/category?bookId={}&_csrfToken=mLzoQhBuJ9pPDdAm8VpBuvMFXCgQQAlhFrC9TuvU'.format(bookId)

headers = {
    'Accept':'application/json, text/plain, */*',
    'Accept-Encoding':'gzip, deflate, br',
    'Accept-Language':'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',
    'Connection':'keep-alive',
    'Cookie':'_csrfToken=mLzoQhBuJ9pPDdAm8VpBuvMFXCgQQAlhFrC9TuvU; newstatisticUUID=1692151465_166373733; Hm_lvt_f00f67093ce2f38f215010b699629083=1692151469; fu=1381548093; _yep_uuid=ad752dda-9748-ea50-e98f-865f3b8bb989; _gid=GA1.2.1689446406.1692151469; supportwebp=true; supportWebp=true; trkf=1; qdrs=0%7C3%7C0%7C0%7C1; navWelfareTime=1692152350314; showSectionCommentGuide=1; qdgd=1; rcr=1036370336; bc=1036370336; e1=%7B%22pid%22%3A%22qd_P_mycenter%22%2C%22eid%22%3A%22qd_H_mall_bottomaddownload%22%2C%22l7%22%3A%22hddl%22%7D; e2=%7B%22l6%22%3A%22%22%2C%22pid%22%3A%22qd_p_qidian%22%2C%22eid%22%3A%22qd_A16%22%2C%22l1%22%3A3%7D; lrbc=1036370336%7C749524263%7C0; _gat_gtag_UA_199934072_2=1; traffic_utm_referer=https%3A%2F%2Flink.csdn.net%2F; Hm_lpvt_f00f67093ce2f38f215010b699629083=1692155018; _ga_FZMMH98S83=GS1.1.1692151469.1.1.1692155018.0.0.0; _ga_PFYW0QLV3P=GS1.1.1692151469.1.1.1692155018.0.0.0; _ga=GA1.2.2023067070.1692151469',
    'Host':'www.qidian.com',
    'Referer':'https://www.qidian.com/chapter/1023826840/573073457/',
    'sec-ch-ua':'"Microsoft Edge";v="113", "Chromium";v="113", "Not-A.Brand";v="24"',
    'sec-ch-ua-mobile':'?0',
    'sec-ch-ua-platform':'"Windows"',
    'Sec-Fetch-Dest':'empty',
    'Sec-Fetch-Mode':'cors',
    'Sec-Fetch-Site':'same-origin',
    'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36 Edg/113.0.1774.42',
    'X-D':'0'
}

response = requests.get(url=url, headers=headers)

또 다른 점은 텍스트를 직접 출력하면 문자가 깨져서 인코딩 형식을 utf-8로 수동으로 설정했다는 것입니다.

response.encoding = 'utf-8'

그런 다음 정규식을 사용하여 텍스트의 id 값과 각 장의 이름을 추출합니다.

ex = '{"uuid":.*?,"cN":"(.*?)","uT":.*?,"cnt":.*?,"cU":.*?,"id":(.*?),"sS":.*?}'
data = re.findall(ex, response.text, re.S)

이 시점에서 우리는 각 장의 URL과 각 장의 이름을 가지고 있습니다.

데이터 추출

다음으로 각 장의 데이터를 추출하겠습니다. 물론 첫 번째 단계는 각 장의 URL을 구성한 다음 각 장의 URL을 요청하는 것입니다. 그런 다음 정규식을 계속 사용하여 새로운 텍스트를 추출합니다. 코드는 다음과 같습니다.

chapterUrlList = []
titleList = []
bookBodyList = []
print("正在获取书籍内容,请耐心等待哦!")
for i in data:
    # 获取每一章的章名
    title = i[0]
    titleList.append(title)
    # 获取每一章的链接地址
    chapterUrl = 'https://www.qidian.com/chapter/{}/'.format(bookId) + i[-1] + '/'
    chapterUrlList.append(chapterUrl)
    # 用正则表达式获取每一章的内容
    book = requests.get(url = chapterUrl)
    ex1 = '<main .*?><p>(.*?)</p></main>'
    bookBody = re.findall(ex1, book.text, re.S)
    # 用replace替换掉小说文本中的一些特殊符号
    body = bookBody[0].replace('\u3000\u3000', '\n')
    body = body.replace('<p>', '')
    body = body.replace('</p>', '')
    # 将文本保存到列表中
    bookBodyList.append(body)
# 获取小说名字和小说作者等信息
book_Info = requests.get(url = chapterUrl)
ex2 = '<script id="vite-plugin-ssr_pageContext" type="application/json">(.*?)</script>'
book_Info = re.findall(ex2, book_Info.text, re.S)[0]
json_data = json.loads(book_Info)
print(type(json_data))
bookName = json_data['pageContext']['pageProps']['pageData']['bookInfo']['bookName']
authorName = json_data['pageContext']['pageProps']['pageData']['bookInfo']['authorName']
print("正在保存,马上就好哦!")

마지막 단계는 요청한 소설 텍스트를 저장하는 것입니다.

with open('书名:'+bookName + '作者' + authorName + '.txt', 'w') as f:
    for j in range(0, len(titleList)):
       f.write(titleList[j])
       f.write(':')
       f.write(chapterUrlList[j])
       f.write(bookBodyList[j])
print("保存成功!")

 이제 끝났습니다!

지침

1. 웹사이트가 어느 정도 역크롤링 되었지만 요청 헤더에 일부 암호화된 매개변수가 있어서 모두 추가하면 데이터를 요청할 수 없으며, 아무것도 추가되지 않으면 데이터를 요청할 수 없습니다.

2. 사실 가장 어려운 점은 데이터 추출인데, 우리가 만든 새로운 텍스트 콘텐츠 중 일부에는 html 구문 기호가 많이 포함되어 있어 이를 정리해야 합니다.

3. 정규식은 사실 맞추기가 쉽지 않거나, 제가 학습을 잘 못하는 것일 수도 있습니다. 응답에서 HTML 표준 코드를 직접 복사하기 때문에 표시할 수 없는 줄바꿈 등의 기호가 포함되어 있습니다. 일치할 때 이러한 제한된 문자를 일치시킬 수 없습니다.

전체 코드는 리소스에 게시하겠습니다. 관심이 있으시면 다운로드하실 수 있습니다.

학습용으로만 사용하시고, 불법적인 목적으로 사용하지 말아주세요.

추천

출처blog.csdn.net/qq_64241302/article/details/132319135