這篇文章主要介紹了python面向對象多線程爬蟲爬取搜狐頁面的實例代碼,需要的朋友可以參考下
首先我們需要幾個包:requests, lxml, bs4, pymongo, redis
- 創建爬蟲對象,具有的幾個行爲:抓取頁面,解析頁面,抽取頁面,儲存頁面
class Spider(object):
def __init__(self):
# 狀態(是否工作)
self.status = SpiderStatus.IDLE
# 抓取頁面
def fetch(self, current_url):
pass
# 解析頁面
def parse(self, html_page):
pass
# 抽取頁面
def extract(self, html_page):
pass
# 儲存頁面
def store(self, data_dict):
pass
- 設置爬蟲屬性,沒有在爬取和在爬取中,我們用一個類封裝, @unique使裏面元素獨一無二,Enum和unique需要從 enum裏面導入:
@unique
class SpiderStatus(Enum):
IDLE = 0
WORKING = 1
- 重寫多線程的類:
class SpiderThread(Thread):
def __init__(self, spider, tasks):
super().__init__(daemon=True)
self.spider = spider
self.tasks = tasks
def run(self):
while True:
pass
- 現在爬蟲的基本結構已經做完了,在main函數創建tasks, Queue需要從queue裏面導入:
def main():
# list沒有鎖,所以使用Queue比較安全, task_queue=[]也可以使用,Queue 是先進先出結構, 即 FIFO
task_queue = Queue()
# 往隊列放種子url, 即搜狐手機端的url
task_queue.put('http://m.sohu,com/')
# 指定起多少個線程
spider_threads = [SpiderThread(Spider(), task_queue) for _ in range(10)]
for spider_thread in spider_threads:
spider_thread.start()
# 控制主線程不能停下,如果隊列裏有東西,任務不能停, 或者spider處於工作狀態,也不能停
while task_queue.empty() or is_any_alive(spider_threads):
pass
print('Over')
4-1. 而 is_any_threads則是判斷線程裏是否有spider還活着,所以我們再寫一個函數來封裝一下:
def is_any_alive(spider_threads):
return any([spider_thread.spider.status == SpiderStatus.WORKING
for spider_thread in spider_threads])
- 所有的結構已經全部寫完,接下來就是可以填補爬蟲部分的代碼,在SpiderThread(Thread)裏面,開始寫爬蟲運行 run 的方法,即線程起來後,要做的事情:
def run(self):
while True:
# 獲取url
current_url = self.tasks_queue.get()
visited_urls.add(current_url)
# 把爬蟲的status改成working
self.spider.status = SpiderStatus.WORKING
# 獲取頁面
html_page = self.spider.fetch(current_url)
# 判斷頁面是否爲空
if html_page not in [None, '']:
# 去解析這個頁面, 拿到列表
url_links = self.spider.parse(html_page)
# 把解析完的結構加到 self.tasks_queue裏面來
# 沒有一次性添加到隊列的方法 用循環添加算求了
for url_link in url_links:
self.tasks_queue.put(url_link)
# 完成任務,狀態變回IDLE
self.spider.status = SpiderStatus.IDLE
- 現在可以開始寫 Spider()這個類裏面的四個方法,首先寫fetch()抓取頁面裏面的:
@Retry()
def fetch(self, current_url, *, charsets=('utf-8', ), user_agent=None, proxies=None):
thread_name = current_thread().name
print(f'[{thread_name}]: {current_url}')
headers = {'user-agent': user_agent} if user_agent else {}
resp = requests.get(current_url,
headers=headers, proxies=proxies)
# 判斷狀態碼,只要200的頁面
return decode_page(resp.content, charsets) \
if resp.status_code == 200 else None
6-1. decode_page是我們在類的外面封裝一個解碼的函數:
def decode_page(page_bytes, charsets=('utf-8',)):
page_html = None
for charset in charsets:
try:
page_html = page_bytes.decode(charset)
break
except UnicodeDecodeError:
pass
# logging.error('Decode:', error)
return page_html
6-2. @retry是裝飾器,用於重試, 因爲需要傳參,在這裏我們用一個類來包裝, 所以最後改成@Retry():
# retry的類,重試次數3次,時間5秒(這樣寫在裝飾器就不用傳參數類), 異常
class Retry(object):
def __init__(self, *, retry_times=3, wait_secs=5, errors=(Exception, )):
self.retry_times = retry_times
self.wait_secs = wait_secs
self.errors = errors
# call 方法傳參
def __call__(self, fn):
def wrapper(*args, **kwargs):
for _ in range(self.retry_times):
try:
return fn(*args, **kwargs)
except self.errors as e:
# 打日誌
logging.error(e)
# 最小避讓 self.wait_secs 再發起請求(最小避讓時間)
sleep((random() + 1) * self.wait_secs)
return None
return wrapper()
- 接下來寫解析頁面的方法,即 parse():
# 解析頁面
def parse(self, html_page, *, domain='m.sohu.com'):
soup = BeautifulSoup(html_page, 'lxml')
url_links = []
# 找body的有 href 屬性的 a 標籤
for a_tag in soup.body.select('a[href]'):
# 拿到這個屬性
parser = urlparse(a_tag.attrs['href'])
netloc = parser.netloc or domain
scheme = parser.scheme or 'http'
netloc = parser.netloc or 'm.sohu.com'
# 只爬取 domain 底下的
if scheme != 'javascript' and netloc == domain:
path = parser.path
query = '?' + parser.query if parser.query else ''
full_url = f'{scheme}://{netloc}{path}{query}'
if full_url not in visited_urls:
url_links.append(full_url)
7-1. 我們需要在SpiderThread()的 run方法裏面,在
current_url = self.tasks_queue.get()
下面添加
visited_urls.add(current_url)
在類外面再添加一個
visited_urls = set()去重
- 現在已經能開始抓取到相應的網址。
.
推薦我們的python學習基地,看老程序是如何學習的!從基礎的python腳本、爬蟲、django、數據挖掘等編程技術,工作經驗,還有前輩精心爲學習python的小夥伴整理零基礎到項目實戰的資料,!每天都有程序員定時講解Python技術,分享一些學習的方法和需要留意的小細節
總結
以上所述是小編給大家介紹的python面向對象多線程爬蟲爬取搜狐頁面的實例代碼,希望對大家有所幫助,如果大家有任何疑問請給我留言,