Python簡單爬蟲小結

網絡爬蟲

網絡爬蟲(又被稱爲網頁蜘蛛,網絡機器人,在FOAF社區中間,更經常的稱爲網頁追逐者),是一種按照一定的規則,自動地抓取萬維網信息的程序或者腳本。

傳統爬蟲從一個或若干初始網頁的URL開始,獲得初始網頁上的URL,在抓取網頁的過程中,不斷從當前頁面上抽取新的URL放入隊列,直到滿足系統的一定停止條件。

爬蟲的合法性

目前法律尚在建立和完善中,現在的爬蟲暫時還是灰色地帶,但是在爬取網站的時候,我們需要注意不要去爬取網站後臺的私密敏感信息,否則容易吃官司。

關於網站的Robots文件

大多數網站都會定義robots.txt文件,對爬蟲進行一些限制。
在網頁域名後面直接加上 ‘/robots.txt’ 然後回車就可以看到該網頁的robots文件,格式如:

User-agent:  Baiduspider
Allow:  /article
Allow:  /oshtml
Disallow:  /product/
Disallow:  /

表示禁止'Baiduspider'爬取除Allow規定的其他頁面

我們在寫爬蟲的時候也應該限制自己的爬蟲遵守所爬取網頁的Robots協議。

一個簡單爬蟲基本流程

數據採集(網頁下載)—> 數據處理(網頁解析)—> 數據存儲(將有用的信息持久化)

  1. 設定抓取目標(種子頁面/起始頁面)並獲取網頁。
  2. 當服務器無法訪問時,按照指定的重試次數嘗試重新下載頁面。
  3. 在需要的時候設置用戶代理或隱藏真實IP,否則可能無法訪問頁面。
  4. 對獲取的頁面進行必要的解碼操作然後抓取出需要的信息。
  5. 在獲取的頁面中通過某種方式(如正則表達式)抽取出頁面中的鏈接信息。
  6. 對鏈接進行進一步的處理(獲取頁面並重覆上面的動作)。
  7. 將有用的信息進行持久化以備後續的處理。

常用工具:

  • 下載數據(種子頁面) - urllib / requests / aiohttp
  • 解析數據 - re / lxml / beautifulsoup4(bs4)/ pyquery
  • 緩存和持久化 - pymysql / redis / sqlalchemy / peewee / pymongo
  • 生成摘要 - hashlib
  • 序列化和壓縮 - pickle / json / zlib
  • 調度器 - 進程 / 線程 / 協程
爬蟲注意事項
  • 處理相對鏈接: 有時候我們從頁面中獲取的鏈接是一個相對鏈接,需要將獲取到的連接與URL前綴進行拼接( urllib.parse中的urljoin() )
  • 設置代理業務:有些網站限制了訪問的區域,一些爬蟲就需要隱藏自己的身份(也就是設置使用代理服務器,urllib.request中的ProxyHandler,國內免費代理服務如:西刺代理)
  • 限制下載速度:如果爬蟲獲取網頁的速度過快,那麼一些網站就會對對應ip進行封禁
  • 避免爬蟲陷阱:一些網站會動態生成內容,這會導致產生無限多的頁面,可以通過記錄到達當前頁面經過了多少個鏈接(鏈接深度)來解決該問題,當達到事先設定的最大深度時爬蟲就不再向隊列中添加該網頁中的鏈接。
  • SSl相關問題:使用urlopen打開一個HTTPS鏈接時會驗證一次SSL證書,如果不做出處理會產生錯誤提示“SSL: CERTIFICATE_VERIFY_FAILED”,可以通過兩種方式加以解決:
    1,使用未經驗證的上下文
import ssl

request = urllib.request.Request(url='...', headers={...}) 
context = ssl._create_unverified_context()
web_page = urllib.request.urlopen(request, context=context)

2,設置全局的取消證書驗證

import ssl

ssl._create_default_https_context = ssl._create_unverified_context
從搜狐體育抓取nba相關新聞標題和鏈接
  • 導入需要用到的模塊
from urllib.error import URLError
from urllib.request import urlopen

import re
import pymysql
import ssl

from pymysql import Error
  • 通過指定的字符集對頁面進行解碼(不是每個網站都將字符集設置爲utf-8)
# charsets=('utf-8',)默認爲utf-8
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
  • 獲取頁面的HTML代碼(通過遞歸實現指定次數的重試操作)
def get_page_html(seed_url, *, retry_times=3, charsets=('utf-8',)):
    page_html = None
    try:
        page_html = decode_page(urlopen(seed_url).read(), charsets)
    except URLError:
        # logging.error('URL:', error)
        if retry_times > 0:
            return get_page_html(seed_url, retry_times=retry_times - 1,
                                 charsets=charsets)
    return page_html
  • 從頁面中提取需要的部分(通常是鏈接,也可以通過正則表達式進行指定)
def get_matched_parts(page_html, pattern_str, pattern_ignore_case=re.I):
    pattern_regex = re.compile(pattern_str, pattern_ignore_case)
    return pattern_regex.findall(page_html) if page_html else []
  • 運行爬蟲程序並對指定的數據進行持久化操作
def start_crawl(seed_url, match_pattern, *, max_depth=-1):
    conn = pymysql.connect(host='localhost', port=3306,
                           database='crawler', user='root',
                           password='123456', charset='utf8')
    try:
        with conn.cursor() as cursor:
            url_list = [seed_url]
            # 通過下面的字典避免重複抓取並控制抓取深度
            visited_url_list = {seed_url: 0}
            while url_list:
                current_url = url_list.pop(0)
                depth = visited_url_list[current_url]
                if depth != max_depth:
                    # 嘗試用utf-8/gbk/gb2312三種字符集進行頁面解碼
                    page_html = get_page_html(current_url, charsets=('utf-8', 'gbk', 'gb2312'))
                    links_list = get_matched_parts(page_html, match_pattern)
                    param_list = []
                    for link in links_list:
                        if link not in visited_url_list:
                            visited_url_list[link] = depth + 1
                            page_html = get_page_html(link, charsets=('utf-8', 'gbk', 'gb2312'))
                            headings = get_matched_parts(page_html, r'<h1>(.*)<span')
                            if headings:
                                param_list.append((headings[0], link))
                    cursor.executemany('insert into tb_result values (default, %s, %s)',
                                       param_list)
                    conn.commit()
    except Error:
        pass
        # logging.error('SQL:', error)
    finally:
        conn.close()


def main():
    ssl._create_default_https_context = ssl._create_unverified_context
    start_crawl('http://sports.sohu.com/nba_a.shtml',
                r'<a[^>]+test=a\s[^>]*href=["\'](.*?)["\']',
                max_depth=2)


if __name__ == '__main__':
    main()
使用BeautifulSoup簡單抓取搜狐體育nba相關頁面
import re

from bs4 import BeautifulSoup

import requests


def main():
    # 通過requests第三方庫的get方法獲取頁面
    resp = requests.get('http://sports.sohu.com/nba_a.shtml')
    # 對響應的字節串(bytes)進行解碼操作(搜狐的部分頁面使用了GBK編碼)
    html = resp.content.decode('gbk')
    # 創建BeautifulSoup對象來解析頁面(相當於javascript的DOM)
    soup = BeautifulSoup(html, 'lxml')
    # 通過CSS先擇器語法查找並通過循環進行處理
    for elem in soup.select('a[test=a]'):
        # 通過attrs屬性(字典)獲取元素的屬性值
        link_url = elem.attrs['href']
        resp = requests.get(link_url)
        bs_sub = BeautifulSoup(resp.text, 'lxml')
        # 使用正則表達式對獲取的數據做進一步的處理
        print(re.sub(r'[\r\n]', '', bs_sub.select_one('h1').text))
        # print(bs_sub.find('h1').text)


if __name__ == '__main__':
    main()
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章