1. 程式人生 > >python從爬蟲基礎到爬取網路小說例項

python從爬蟲基礎到爬取網路小說例項

 

 

一.爬蟲基礎

 1.1 requests類

  1.1.1 request的7個方法

requests.request() 例項化一個物件,擁有以下方法

 

requests.get(url, *args)

requests.head() 頭資訊

 

requests.post()

requests.put()

requests.patch() 修改一部分內容

 

requests.delete()

 

url = "http://quanben5.com/n/doupocangqiong/6.html
" headers = { "User-Agent":"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.81 Safari/537.36"
} r = requests.get(url, headers=headers) data = { "pinyin": "doupocangqiong", "content_id": "4", } r = requests.post(url, data=data, headers=headers)

 

 

  1.1.2*arg裡面的引數

params 字典或者位元組序列,作為引數增加到url中
data 字典位元組序列檔案物件, 放在url裡面對應的地方
json 作為requests的內容
headers 字典 模擬服務頭
cookies 字典 cookieJar
auth 元組
files 字典型別,傳輸檔案
timeout 設定的超時時間
proxies 字典型別,設定訪問代理伺服器,可以增加登入認證 pxs={"http":"http://user:[email protected]"}
allow_redirects 預設True 是否允許重定向
stream 預設TRUE 獲得資料立刻下載
verity 預設True 認證SSL證書開關
cert 本地SSL證書路徑

 

1.2 BeautifulSoup類

 

soup = BeautifulSoup("","html.parser")
soup.prettify()
soup.find_all(name, attrs, recursive, string, **kwargs)
name: 標籤名稱
attrs: 屬性
string: 檢索字串

soup.head
soup.head.contents 列表
soup.body.contents[1]
soup.body.children
soup.body.descendants

.parent
.parents
.next_sibling 下一個
.previous_sibling 上一個
.next_siblings 下一個所有 迭代
.previous_siblings 上一個所有

 

二.實戰

 

首先要找到可以線上看小說的網頁

這裡我隨便百度了一下,首先選擇了一個全本5200小說網("https://www.qb5200.tw")

開啟某小說章節目錄表

https://www.qb5200.tw/xiaoshuo/0/357/

檢視原始碼

發現正文卷是在class為listmain的div下面的第二個dt標籤裡

之後的路徑標籤為a

1     url_text = soup.find_all('div', 'listmain')[0]  # 找到第一個class=listmain的div標籤
2     main_text = url_text.find_all('dt')[1]  # 找到下面的第二個dt標籤
3     for tag in main_text.next_siblings:
4         try:
5             url = ''.join(['https://', host, tag.a.attrs['href']])
6             print('parsering', url)
7         except:
8             continue
找到每個章節的url

在隨便開啟一章

檢視原始碼為:

 1 def getHTMLText(url, headers={}):
 2     """
 3     獲取網站原始碼
 4     :param url:
 5     :return: class response
 6     """
 7     if headers != {}:
 8         headers = {"User-Agent":"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.81 Safari/537.36"}
 9 
10     # proxies = get_random_ip()
11     proxies = {}
12     try:
13         # print("start", url)
14         r = requests.get(url, proxies=proxies, headers=headers)
15         r.raise_for_status()
16         # r.encoding = r.apparent_encoding
17         # print('end', url)
18         return r
19     except:
20         return r.status_code
21 
22 def parseByQB5200(soup, host, f):
23     """
24     在全本小說網的原始碼下爬取小說
25     :param soup, host: 
26     :param f: 
27     :return: 
28     """
29     url_text = soup.find_all('div', 'listmain')[0]
30     main_text = url_text.find_all('dt')[1]
31     x = 0
32     for tag in main_text.next_siblings:
33         time.sleep(1)
34         try:
35             url = ''.join(['https://', host, tag.a.attrs['href']])
36             print('parsering', url)
37             soup = BeautifulSoup(getHTMLText(url).text, "html.parser")
38             passage = soup.find_all("div", "content")[0]
39             title = passage.h1.string
40             f.writelines(''.join([title, '\n']))
41             passage = soup.find_all("div", "showtxt")[0]
42             for i in passage.descendants:
43                 if i.name != "br":
44                     st = i.string
45                     if st.strip().startswith('http') or st.strip().startswith('請記住本書'):
46                         continue
47                     f.writelines(''.join(['  ', st.strip(), '\n']))
48             x += 1
49             print('%d.%s 下載完成' % (x, title))
50         except:
51             continue
52 
53 def getNovelUrls(url):
54     """
55     通過小說的目錄網址判斷小說所在的網站
56     並呼叫屬於該網站的爬蟲語句
57     :param url:
58     :return:
59     """
60 
61     response = getHTMLText(url)
62     host = url.split('//')[1].split('/')[0]
63     host_list = {
64         "www.qb5200.tw": parseByQB5200,
65         # "www.qu.la": parseByQuLa,
66         "quanben5.com": parseByQB5
67     }
68     print(response)
69     soup = BeautifulSoup(response.text, 'html.parser')
70     with open('1.txt', 'w', encoding='utf8') as f:
71         host_list[host](soup, host, f)
72 
73 if __name__ == '__main__':
74     getNovelUrls("https://www.qb5200.tw/xiaoshuo/0/357/")
在全本5200爬取小說txt

問題在於

全本小說網("www.qb5200.tw")只允許爬3次,之後就403錯誤

解決方法暫時未知

 

 

因此換了一個網站

全本5小說網("quanben5.com")

同樣的方法搜尋原始碼

 

然而發現了問題

給出的html頁面只有一半的原始碼

因此按F12開啟檢查

 

發現所有的文字存在這個xhr裡

 

 

點選檢視請求頭資訊

 

 

發現是post請求

請求的url是/index.php?c=book&a=ajax_content

請求的資料在最下面的form表單裡

開啟網頁原始檔和js檔案,搜尋這些表單資訊

分別在ajax.js裡和原始檔裡找到了這些

 

 

原始檔裡面的可以直接生成data資料表單

在ajax.js裡可以知道rndval欄位是當前時間,精確到毫秒

 

 

 優化

採用了gvent進行非同步IO處理,每一張網頁儲存在temp裡面,最後將檔案合成一個txt

程式碼如下:

 

  1 #!/usr/bin/env python
  2 # encoding: utf-8
  3 
  4 """
  5 @version: 
  6 @author: Wish Chen
  7 @contact: [email protected]
  8 @file: get_novels.py
  9 @time: 2018/11/21 19:43
 10 
 11 """
 12 import gevent
 13 from gevent import monkey
 14 monkey.patch_all()
 15 import requests, time, random, re, os
 16 from bs4 import BeautifulSoup
 17 
 18 dir = os.path.dirname(os.path.abspath(__file__))
 19 
 20 def getHTMLText(url, data=[], method='get'):
 21     """
 22     獲取網站的原始碼
 23     請求預設為get
 24     :param url: 
 25     :param data: 
 26     :param method: 
 27     :return: 
 28     """
 29     headers = {"User-Agent":"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.81 Safari/537.36"}
 30     proxies = {}
 31     try:
 32         # print("start", url)
 33         r = requests.request(method, url, proxies=proxies, headers=headers, data=data)
 34         r.raise_for_status()
 35         # r.encoding = r.apparent_encoding
 36         # print('end', url)
 37         return r
 38     except:
 39         return r.status_code
 40 
 41 
 42 def fetch_async(x, url):
 43     """
 44     非同步IO所需要執行的操作
 45     獲取原始檔
 46     模擬向ajax請求獲取完整文字
 47     每一章輸入到temp資料夾下
 48     :param x:
 49     :param url:
 50     :return:
 51     """
 52     url_main = "http://quanben5.com/index.php?c=book&a=ajax_content"
 53     r = getHTMLText(url)  # 獲取每一章的原始檔
 54     title = re.search(r'<h1 class="title1">(.*)</h1>', r.text).group(1)
 55     result = re.search(r'<script type="text/javascript">ajax_post\((.*)\)</script>', r.text).group(1)
 56     num_list = result.split("','")
 57     num_list[9] = num_list[9][:-1]
 58     content = {}  # 開始模擬post請求傳送的表單
 59     for i in range(1, 5):
 60         content[num_list[i * 2]] = num_list[i * 2 + 1]
 61     content['_type'] = "ajax"
 62     content['rndval'] = int(time.time() * 1000)
 63     r = getHTMLText(url_main, data=content, method='post')  # 模擬post請求
 64     soup = BeautifulSoup(r.text, "lxml")
 65     with open(os.path.join(dir, 'temp', "%s.txt" % x), "w") as f:
 66         f.writelines(''.join([str(x), '.', title, '\n\n']))
 67         for tag in soup.body.children:
 68             if tag.name == 'p':
 69                 f.writelines(''.join(['    ', tag.string.strip(), '\n\n']))
 70     print('%d.%s 下載完成' % (x, title))
 71 
 72 
 73 
 74 def get_together(name, author):
 75     """
 76     將temp目錄下的各網頁下載下來的txt
 77     合併在一起
 78     並刪除temp檔案
 79     :param name:
 80     :param author:
 81     :return:
 82     """
 83     with open(os.path.join(dir, "%s.txt" % name), "w") as f:
 84         f.writelines(''.join([name, '\n\n作者:', author, '\n\n']))
 85         for i in range(len(os.listdir(os.path.join(dir, 'temp')))):
 86             f.write(open(os.path.join(dir, 'temp', "%s.txt" % (i+1)), "r").read())
 87             f.write('\n\n')
 88             # os.remove(os.path.join(dir, 'temp', "%s.txt" % (i+1)))
 89 
 90 
 91 def parseByQB5(response, host):
 92     """
 93     在全本5小說網的原始碼下爬取小說
 94     獲得書名和作者
 95     採用gevent非同步IO優化
 96     :param response:
 97     :param host:
 98     :return:
 99     """
100     soup = BeautifulSoup(response.text, 'html.parser')
101     url_text = soup.find_all('div', 'box')[2]
102     main_text = url_text.find_all('h2')[0].next_sibling
103     url_list = []
104     for tag in main_text.descendants:
105         if tag.name == 'li':
106             url = ''.join(['http://', host, tag.a.attrs['href']])
107             url_list.append(url)
108     from gevent.pool import Pool
109     pool = Pool(20)
110     gevent.joinall([pool.spawn(fetch_async, i+1, url=url_list[i]) for i in range(len(url_list))])
111     name = re.search(r"<h3><span>(.*)</span></h3>", response.text).group(1)
112     author = re.search(r'<span class="author">(.*)</span></p>', response.text).group(1)
113     get_together(name, author)
114 
115 
116 
117 
118 def getNovelUrls(url):
119     """
120     通過小說的目錄網址判斷小說所在的網站
121     並呼叫屬於該網站的爬蟲語句
122     :param url:
123     :return:
124     """
125 
126     response = getHTMLText(url)
127     host = url.split('//')[1].split('/')[0]
128     host_list = {
129         "quanben5.com": parseByQB5
130     }
131     host_list[host](response, host)
132 
133 
134 if __name__ == '__main__':
135     url_list = [
136         "https://www.qu.la/book/365/",
137         "https://www.qb5200.tw/xiaoshuo/0/357/",
138         "http://quanben5.com/n/doupocangqiong/xiaoshuo.html",
139         "http://quanben5.com/n/dazhuzai/xiaoshuo.html",
140         "http://quanben5.com/n/douluodalu/xiaoshuo.html",
141         "http://quanben5.com/n/renxingderuodian/xiaoshuo.html"
142     ]
143     if not os.path.exists('temp'):
144         os.mkdir('temp')
145     getNovelUrls(url_list[5])
非同步IO優化

 

封裝性還不夠好

最好一個網站用一個類來封裝

 

採用scrapy框架

正在設計中...

未解決問題:

代理IP問題:

使用的是免費的代理

還是無法解決只能下載3個頁面之後報錯503的問題

搜尋功能:

模擬搜尋介面提交請求,在返回頁中列出所有小說的名字和作者以及簡介

可以簡單實現

 多網站定製

 要觀察各個網站的目錄原始碼結構以及文章原始碼結構

每一個網站都可以用一個parse函式來解析其內容