溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

python面向?qū)ο蠖嗑€程爬蟲爬取搜狐頁面的示例分析

發(fā)布時間:2021-07-22 10:03:22 來源:億速云 閱讀:119 作者:小新 欄目:開發(fā)技術(shù)

這篇文章主要為大家展示了“python面向?qū)ο蠖嗑€程爬蟲爬取搜狐頁面的示例分析”,內(nèi)容簡而易懂,條理清晰,希望能夠幫助大家解決疑惑,下面讓小編帶領(lǐng)大家一起研究并學習一下“python面向?qū)ο蠖嗑€程爬蟲爬取搜狐頁面的示例分析”這篇文章吧。

首先我們需要幾個包:requests, lxml, bs4, pymongo, redis

1. 創(chuàng)建爬蟲對象,具有的幾個行為:抓取頁面,解析頁面,抽取頁面,儲存頁面

class Spider(object):
 def __init__(self):
  # 狀態(tài)(是否工作)
  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

2. 設(shè)置爬蟲屬性,沒有在爬取和在爬取中,我們用一個類封裝, @unique使里面元素獨一無二,Enum和unique需要從 enum里面導(dǎo)入:

@unique
class SpiderStatus(Enum):
 IDLE = 0
 WORKING = 1

3. 重寫多線程的類:

class SpiderThread(Thread):
 def __init__(self, spider, tasks):
  super().__init__(daemon=True)
  self.spider = spider
  self.tasks = tasks
 def run(self):
  while True:
   pass

4. 現(xiàn)在爬蟲的基本結(jié)構(gòu)已經(jīng)做完了,在main函數(shù)創(chuàng)建tasks, Queue需要從queue里面導(dǎo)入:

def main():
 # list沒有鎖,所以使用Queue比較安全, task_queue=[]也可以使用,Queue 是先進先出結(jié)構(gòu), 即 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()
 # 控制主線程不能停下,如果隊列里有東西,任務(wù)不能停, 或者spider處于工作狀態(tài),也不能停
 while task_queue.empty() or is_any_alive(spider_threads):
  pass
 print('Over')

4-1. 而 is_any_threads則是判斷線程里是否有spider還活著,所以我們再寫一個函數(shù)來封裝一下:

def is_any_alive(spider_threads):
 return any([spider_thread.spider.status == SpiderStatus.WORKING
    for spider_thread in spider_threads])

5. 所有的結(jié)構(gòu)已經(jīng)全部寫完,接下來就是可以填補爬蟲部分的代碼,在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)
    # 把解析完的結(jié)構(gòu)加到 self.tasks_queue里面來
    # 沒有一次性添加到隊列的方法 用循環(huán)添加算求了
    for url_link in url_links:
     self.tasks_queue.put(url_link)
   # 完成任務(wù),狀態(tài)變回IDLE
   self.spider.status = SpiderStatus.IDLE

6.  現(xiàn)在可以開始寫 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)
  # 判斷狀態(tài)碼,只要200的頁面
  return decode_page(resp.content, charsets) \
   if resp.status_code == 200 else None

6-1. decode_page是我們在類的外面封裝一個解碼的函數(shù):

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的類,重試次數(shù)3次,時間5秒(這樣寫在裝飾器就不用傳參數(shù)類), 異常
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 再發(fā)起請求(最小避讓時間)
     sleep((random() + 1) * self.wait_secs)
   return None
  return wrapper()

7. 接下來寫解析頁面的方法,即 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)
  return url_links

7-1. 我們需要在SpiderThread()的 run方法里面,在

current_url = self.tasks_queue.get()

下面添加

visited_urls.add(current_url)

在類外面再添加一個

visited_urls = set()去重

8. 現(xiàn)在已經(jīng)能開始抓取到相應(yīng)的網(wǎng)址。

python面向?qū)ο蠖嗑€程爬蟲爬取搜狐頁面的示例分析 

以上是“python面向?qū)ο蠖嗑€程爬蟲爬取搜狐頁面的示例分析”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對大家有所幫助,如果還想學習更多知識,歡迎關(guān)注億速云行業(yè)資訊頻道!

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI