【Python之路】第二十四篇--爬蟲

 

  網絡爬蟲(又被稱爲網頁蜘蛛,網絡機器人,在FOAF社區中間,更常常的稱爲網頁追逐者),是一種按照必定的規則,自動地抓取萬維網信息的程序或者腳本。另一些不常使用的名字還有螞蟻、自動索引、模擬程序或者蠕蟲。html

Requests           👉官方文檔

  Python標準庫中提供了:urllib、urllib二、httplib等模塊以供Http請求,可是,它的 API 太渣了。它是爲另外一個時代、另外一個互聯網所建立的。它須要巨量的工做,甚至包括各類方法覆蓋,來完成最簡單的任務。python

  Requests 是使用 Apache2 Licensed 許可證的 基於Python開發的HTTP 庫,其在Python內置模塊的基礎上進行了高度的封裝,從而使得Pythoner進行網絡請求時,變得美好了許多,使用Requests能夠垂手可得的完成瀏覽器可有的任何操做。git

一、GET請求github

# 一、無參數實例
import requests
 
ret = requests.get('https://github.com/timeline.json')
 
print(ret.url)
print(ret.text)
 
# 二、有參數實例
import requests
 
payload = {'key1': 'value1', 'key2': 'value2'}
ret = requests.get("http://httpbin.org/get", params=payload)
 
print(ret.url)
print(ret.text)

二、POST請求正則表達式

# 一、基本POST實例
import requests
 
payload = {'key1': 'value1', 'key2': 'value2'}
ret = requests.post("http://httpbin.org/post", data=payload)
 
print(ret.text)
 
# 二、發送請求頭和數據實例
import requests
import json
 
url = 'https://api.github.com/some/endpoint'
payload = {'some': 'data'}
headers = {'content-type': 'application/json'}
 
ret = requests.post(url, data=json.dumps(payload), headers=headers)
 
print(ret.text)
print(ret.cookies)

三、其餘請求數據庫

requests.get(url, params=None, **kwargs)
requests.post(url, data=None, json=None, **kwargs)
requests.put(url, data=None, **kwargs)
requests.head(url, **kwargs)
requests.delete(url, **kwargs)
requests.patch(url, data=None, **kwargs)
requests.options(url, **kwargs)
 
# 以上方法均是在此方法的基礎上構建
requests.request(method, url, **kwargs)

四、響應內容json

Requests 會自動解碼來自服務器的內容。大多數 unicode 字符集都能被無縫地解碼。windows

請求發出後,Requests 會基於 HTTP 頭部對響應的編碼做出有根據的推測。api

當你訪問 r.text 之時,Requests 會使用其推測的文本編碼。你能夠找出 Requests 使用了什麼編碼,而且可以使用 r.encoding 屬性來改變它:瀏覽器

response.encoding = 'ISO-8859-1'

以字節的方式訪問請求響應體

response.content
  • response.url
  • response.encoding 編碼
  • response.status_code 狀態嗎
  • response.headers headers
  • response.text 返回的文本
  • response.json() json
  • response.raw 原始數據
  • response.raise_for_status() 錯誤請求信息
  • response.cookies
  • response.history

五、Cookie

設置cookies:

requests.get('http://www.google.com/', cookies={'key1': 'value1', 'key2': 'value2'})

若是使用Session的話,能夠這樣設置

session = requests.session()
cookies_dict = {'key1': 'value1', 'key2': 'value2'}
session.cookies = requests.utils.cookiejar_from_dict(cookies)

保存Cookies:

cookies_dict = requests.utils.dict_from_cookiejar(session.cookies)

普通方式:

import requests

response = requests.get('http://www.baidu.com')
cookies = response.cookies

# 字典形式存儲
cookies_dict = response.cookies.get_dict()

另外提供一個Chrome瀏覽器中的Cookies字符串轉字典的函數

def cookie_dict_form_text(cookie_text):
    cookies = {}
    for cookie_unit_text in cookie_text.split('; '):
        cookie_unit = cookie_unit_text.split('=')
        if len(cookie_unit) >= 2:
            cookies[cookie_unit[0]] = cookie_unit[1]
    return cookies
View Code

六、會話對象Session

會話對象讓你可以跨請求保持某些參數。它也會在同一個 Session 實例發出的全部請求之間保持 cookie, 

conn = requests.session()
r = conn.get('http://dig.chouti.com/')
print(r.request.headers)

r = conn.get('http://dig.chouti.com/')
print(r.request.headers)

#{'User-Agent': 'python-requests/2.12.4', 'Connection': 'keep-alive', 'Accept': '*/*', 'Accept-Encoding': 'gzip, deflate'}
#{'User-Agent': 'python-requests/2.12.4', 'Connection': 'keep-alive', 'Accept': '*/*', 'Accept-Encoding': 'gzip, deflate', 'Cookie': 'gpsd=6392a33cd7960ef59be44158c19f58f8; route=e7bfb38cb3b5a04758a9513df6738c7e; JSESSIONID=aaaZZDnAXDsv6eA-7rHTv'}

在第二次訪問時,已經自動添加上了cookies

七、py2版本下亂碼問題

response.content.decode("utf8","ignore").encode("gbk","ignore") 就不會有亂碼了

這裏所使用的ignore屬性意思是忽略其中有異常的編碼,僅顯示有效的編碼。py2下亂碼問題能夠參考   👉點我

八、使用代理proxies

import requests   
proxies = { "http": "http://10.10.1.10:3128", "https": "http://10.10.1.10:1080", }   
requests.get("http://example.org", proxies=proxies) 

這裏的能夠經過ip測試網站進行驗證是否成功使用了代理 http://ip.chinaz.com/(查看你訪問後的網站的您的IP是多少來判斷)

九、其餘

requests模塊已經將經常使用的Http請求方法爲用戶封裝完成,用戶直接調用其提供的相應方法便可,其中方法的全部參數有:

def request(method, url, **kwargs):
    """Constructs and sends a :class:`Request <Request>`.

    :param method: method for the new :class:`Request` object.
    :param url: URL for the new :class:`Request` object.
    :param params: (optional) Dictionary or bytes to be sent in the query string for the :class:`Request`.
    :param data: (optional) Dictionary, bytes, or file-like object to send in the body of the :class:`Request`.
    :param json: (optional) json data to send in the body of the :class:`Request`.
    :param headers: (optional) Dictionary of HTTP Headers to send with the :class:`Request`.
    :param cookies: (optional) Dict or CookieJar object to send with the :class:`Request`.
    :param files: (optional) Dictionary of ``'name': file-like-objects`` (or ``{'name': ('filename', fileobj)}``) for multipart encoding upload.
    :param auth: (optional) Auth tuple to enable Basic/Digest/Custom HTTP Auth.
    :param timeout: (optional) How long to wait for the server to send data
        before giving up, as a float, or a :ref:`(connect timeout, read
        timeout) <timeouts>` tuple.
    :type timeout: float or tuple
    :param allow_redirects: (optional) Boolean. Set to True if POST/PUT/DELETE redirect following is allowed.
    :type allow_redirects: bool
    :param proxies: (optional) Dictionary mapping protocol to the URL of the proxy.
    :param verify: (optional) whether the SSL cert will be verified. A CA_BUNDLE path can also be provided. Defaults to ``True``.
    :param stream: (optional) if ``False``, the response content will be immediately downloaded.
    :param cert: (optional) if String, path to ssl client cert file (.pem). If Tuple, ('cert', 'key') pair.
    :return: :class:`Response <Response>` object
    :rtype: requests.Response

    Usage::

      >>> import requests
      >>> req = requests.request('GET', 'http://httpbin.org/get')
      <Response [200]>
    """

    # By using the 'with' statement we are sure the session is closed, thus we
    # avoid leaving sockets open which can trigger a ResourceWarning in some
    # cases, and look like a memory leak in others.
    with sessions.Session() as session:
        return session.request(method=method, url=url, **kwargs)
View Code

更多requests模塊相關的文檔見:http://cn.python-requests.org/zh_CN/latest/

Scrapy

Scrapy是一個爲了爬取網站數據,提取結構性數據而編寫的應用框架。 其能夠應用在數據挖掘,信息處理或存儲歷史數據等一系列的程序中。
其最初是爲了頁面抓取 (更確切來講, 網絡抓取 )所設計的, 也能夠應用在獲取API所返回的數據(例如 Amazon Associates Web Services ) 或者通用的網絡爬蟲。Scrapy用途普遍,能夠用於數據挖掘、監測和自動化測試。

Scrapy 使用了 Twisted異步網絡庫來處理網絡通信。總體架構大體以下

Scrapy主要包括瞭如下組件:

  • 引擎(Scrapy)
    用來處理整個系統的數據流處理, 觸發事務(框架核心)
  • 調度器(Scheduler)
    用來接受引擎發過來的請求, 壓入隊列中, 並在引擎再次請求的時候返回. 能夠想像成一個URL(抓取網頁的網址或者說是連接)的優先隊列, 由它來決定下一個要抓取的網址是什麼, 同時去除重複的網址
  • 下載器(Downloader)
    用於下載網頁內容, 並將網頁內容返回給蜘蛛(Scrapy下載器是創建在twisted這個高效的異步模型上的)
  • 爬蟲(Spiders)
    爬蟲是主要幹活的, 用於從特定的網頁中提取本身須要的信息, 即所謂的實體(Item)。用戶也能夠從中提取出連接,讓Scrapy繼續抓取下一個頁面
  • 項目管道(Pipeline)
    負責處理爬蟲從網頁中抽取的實體,主要的功能是持久化實體、驗證明體的有效性、清除不須要的信息。當頁面被爬蟲解析後,將被髮送到項目管道,並通過幾個特定的次序處理數據。
  • 下載器中間件(Downloader Middlewares)
    位於Scrapy引擎和下載器之間的框架,主要是處理Scrapy引擎與下載器之間的請求及響應。
  • 爬蟲中間件(Spider Middlewares)
    介於Scrapy引擎和爬蟲之間的框架,主要工做是處理蜘蛛的響應輸入和請求輸出。
  • 調度中間件(Scheduler Middewares)
    介於Scrapy引擎和調度之間的中間件,從Scrapy引擎發送到調度的請求和響應。

Scrapy運行流程大概以下:

  1. 引擎從調度器中取出一個連接(URL)用於接下來的抓取

  2. 引擎把URL封裝成一個請求(Request)傳給下載器

  3. 下載器把資源下載下來,並封裝成應答包(Response)

  4. 爬蟲解析Response

  5. 解析出實體(Item),則交給實體管道進行進一步的處理

  6. 解析出的是連接(URL),則把URL交給調度器等待抓取

1、安裝

1.安裝 setuptools

setuptools 官網下載

python ez_setup.py

2.安裝 pip

下載pip源碼 官網下載,安裝

python setup.py install

3.安裝 scrapy

pip install requests
pip install scrapy

windows下

安裝依賴scrapy的組件

http://www.lfd.uci.edu/~gohlke/pythonlibs/,Ctrl+F搜索 lxml、Twisted、Scrapy,下載對應的版本,

例如:lxml-3.7.3-cp35-cp35m-win_adm64.whl,表示lxml的版本爲3.7.3,對應的python版本爲3.5-64bit。

a.pip3 install wheel

b.pip3 install lxml-3.7.3-cp35-cp35m-win_amd64.whl

c.pip3 install Twisted-17.1.0-cp35-cp35m-win_amd64.whl

d.pip3 install Scrapy-1.3.2-py2.py3-none-any.whl

Srapy已經安裝成功,還要下載pywin32,找到對應版本下載,一路下一步安裝便可。安裝完成後,就能夠正常使用Scrapy了。

URL:https://sourceforge.net/projects/pywin32/files/pywin32/Build%20220/

2、基本使用

一、建立項目

scrapy startproject your_project_name

自動建立目錄:

project_name/
   scrapy.cfg
   project_name/
       __init__.py
       items.py
       pipelines.py
       settings.py
       spiders/
           __init__.py

文件說明:

  • scrapy.cfg  項目的配置信息,主要爲Scrapy命令行工具提供一個基礎的配置信息。(真正爬蟲相關的配置信息在settings.py文件中)

  • items.py    設置數據存儲模板,用於結構化數據,如:Django的Model

  • pipelines    數據處理行爲,如:通常結構化的數據持久化

  • settings.py 配置文件,如:遞歸的層數、併發數,延遲下載等

  • spiders      爬蟲目錄,如:建立文件,編寫爬蟲規則

注意:通常建立爬蟲文件時,以網站域名命名

二、基本選擇器Selector

一個/ 表示根目錄 

//div[@class='item_list']/
//div[@class='item_list']/div  孩子
//div[@class='item_list']//div  子子孫孫

# 多屬性匹配
/div[@class='item_list'][@id='a1']/

# 獲取內容
//div[@class='item_list']//div/text()  

# 獲取屬性
//div[@class='item_list']//img/@src
//a/@href

# 索引方式
//div[@class='item_list']/div[1]  默認從第1個開始
//div[@class='item_list']/div[2]  

正則表達式使用:

<body>
    <li class="item-"><a href="link.html">first item</a></li>
    <li class="item-0"><a href="link1.html">first item</a></li>
    <li class="item-1"><a href="link2.html">second item</a></li>
</body>

類型一:
ret = Selector(response=response).xpath('//li[re:test(@class, "item-\d*")]//@href').extract()

類型二:
ret = Selector(response=response).xpath('//.select('div//a[1]').re('xx:(\w+)') # 不用寫.extract()

更多選擇器規則:http://scrapy-chs.readthedocs.io/zh_CN/latest/topics/selectors.html

三、編寫爬蟲

import scrapy

class XiaoHuarSpider(scrapy.spiders.Spider):
    name = "xiaohuar"       # spider_name
    allowed_domains = ["xiaohuar.com"]
    start_urls = [
        "http://www.xiaohuar.com/hua/",
    ]
 
    def parse(self, response):
        print(response, type(response))

        current_url = response.url
        body = response.body
        unicode_body = response.body_as_unicode()
        print(unicode_body )

四、運行

進入project_name目錄,運行命令

scrapy crawl spider_name --nolog

五、兩種定義查找的方式

from scrapy.selector import Selector
ret = Selector(response=response).xpath('//li[re:test(@class, "item-\d*")]//@href').extract()

from scrapy.selector import HtmlXPathSelector
hxs = HtmlXPathSelector(response)
items = hxs.select('//div[@class="item_list infinite_scroll"]/div').extract()

五、去除重複url

通常採用md5 加密url 存放到set() 或數據庫裏

import hashlib

url_set = set()

md5_obj = hashlib.md5()
md5_obj.update(response.url)
md5_url = md5_obj.hexdigest()
if md5_url in url_set :
    pass
else:
    pass

六、遞歸的訪問

以上的爬蟲僅僅是爬去初始頁,而咱們爬蟲是須要源源不斷的執行下去,直到全部的網頁被執行完畢

#!/usr/bin/env python
# -*-coding:utf-8 -*-
from scrapy.selector import Selector
from scrapy.http import Request
from scrapy.selector import HtmlXPathSelector
import scrapy
import os
import requests
import hashlib

class XiaoHuarSpider(scrapy.spiders.Spider):
    name = "xiaohuar"
    url_set = set()
    allowed_domains = ["xiaohuar.com"]

    start_urls = [
        "http://www.xiaohuar.com/hua/",
    ]

    def parse(self, response):
        # 分析頁面
        # 找到頁面中符合規則的內容(校花圖片),保存
        # 找到全部的a標籤,再訪問其餘a標籤,一層一層的搞下去
        md5_obj = hashlib.md5()
        md5_obj.update(bytes(response.url,encoding='utf-8'))
        md5_url = md5_obj.hexdigest()
        if md5_url in XiaoHuarSpider.url_set:
            pass
        else:
            hxs = HtmlXPathSelector(response)
            items = hxs.select('//div[@class="item_list infinite_scroll"]/div')      #對象
            for i in range(len(items)):
                src = hxs.select(
                    '//div[@class="item_list infinite_scroll"]/div[%d]//div[@class="img"]/a/img/@src' % i).extract()
                name = hxs.select(
                    '//div[@class="item_list infinite_scroll"]/div[%d]//div[@class="img"]/span/text()' % i).extract()
                school = hxs.select(
                    '//div[@class="item_list infinite_scroll"]/div[%d]//div[@class="img"]/div[@class="btns"]/a/text()' % i).extract()
                print(name,school,src)
                if src and name and school:
                    ab_src = "http://www.xiaohuar.com" + src[0]
                    file_name = "%s_%s.jpg" % (school[0],name[0])
                    file_path = os.path.join("E:/image", file_name)
                    f = open(file_path, 'wb')
                    f.write(requests.get(ab_src).content)
                    f.close()

            current_page_urls = hxs.select('//a/@href').extract()
            for i in range(len(current_page_urls)):
                url = current_page_urls[i]
                if url.startswith('http://www.xiaohuar.com/list-1-'):
                    url_ab = url
                    yield Request(url_ab, callback=self.parse)

以上代碼將符合規則的頁面中的圖片保存在指定目錄,而且在HTML源碼中找到全部的其餘 a 標籤的href屬性,從而「遞歸」的執行下去,直到全部的頁面都被訪問過爲止。以上代碼之因此能夠進行「遞歸」的訪問相關URL,關鍵在於parse方法使用了 yield Request對象。

注:能夠修改settings.py 中的配置文件,以此來指定「遞歸」的層數,如: DEPTH_LIMIT = 1

七、格式化處理

上述實例只是簡單的圖片處理,因此在parse方法中直接處理。若是對於想要獲取更多的數據(獲取頁面的價格、商品名稱、QQ等),則能夠利用Scrapy的items將數據格式化,而後統一交由pipelines來處理。

在items.py中建立類:

import scrapy

class Scrapyapp1Item(scrapy.Item):
    src = scrapy.Field()
    name = scrapy.Field()
    school = scrapy.Field()

上述定義模板,之後對於從請求的源碼中獲取的數據贊成按照此結構來獲取,因此在spider中須要有一下操做:

#!/usr/bin/env python
# -*-coding:utf-8 -*-
from scrapy.selector import Selector
from scrapy.http import Request
from scrapy.selector import HtmlXPathSelector
import scrapy
import hashlib

class XiaoHuarSpider(scrapy.spiders.Spider):
    name = "xiaohuar"
    url_set = set()
    allowed_domains = ["xiaohuar.com"]

    start_urls = [
        "http://www.xiaohuar.com/hua/",
    ]

    def parse(self, response):
        # 分析頁面
        # 找到頁面中符合規則的內容(校花圖片),保存
        # 找到全部的a標籤,再訪問其餘a標籤,一層一層的搞下去
        md5_obj = hashlib.md5()
        md5_obj.update(bytes(response.url,encoding='utf-8'))
        md5_url = md5_obj.hexdigest()
        if md5_url in XiaoHuarSpider.url_set:
            pass
        else:
            hxs = HtmlXPathSelector(response)
            items = hxs.select('//div[@class="item_list infinite_scroll"]/div')      #對象
            for i in range(len(items)):
                src = hxs.select(
                    '//div[@class="item_list infinite_scroll"]/div[%d]//div[@class="img"]/a/img/@src' % i).extract()
                name = hxs.select(
                    '//div[@class="item_list infinite_scroll"]/div[%d]//div[@class="img"]/span/text()' % i).extract()
                school = hxs.select(
                    '//div[@class="item_list infinite_scroll"]/div[%d]//div[@class="img"]/div[@class="btns"]/a/text()' % i).extract()
                # print(name,school,src)
                if src and name and school:
                    from scrapyapp1 import items
                    obj = items.Scrapyapp1Item()
                    obj['src'] = src[0]
                    obj['name'] = name[0]
                    obj['school'] = school[0]
                    yield obj
View Code

此處代碼的關鍵在於:

  • 將獲取的數據封裝在了Item對象中

  • yield Item對象 (一旦parse中執行yield Item對象,則自動將該對象交個pipelines的類來處理)

import os
import requests

class Scrapyapp1Pipeline(object):
    def process_item(self, item, spider):

        print(item['name'],item['school'],item['src'])
        ab_src = "http://www.xiaohuar.com" + item['src']
        file_name = "%s_%s.jpg" % (item['school'],item['name'])
        file_path = os.path.join("E:/image", file_name)
        f = open(file_path, 'wb')
        f.write(requests.get(ab_src).content)
        f.close()
        return item
pipelines 類

若是有多個pipelines類,到底Scapy會自動執行那個?哈哈哈哈,固然須要先配置了,否則Scapy就蒙逼了。。。

在settings.py中作以下配置:

ITEM_PIPELINES = {
    'scrapyapp1.pipelines.Scrapyapp1Pipeline': 100,
    ....,
}
# 每行後面的整型值,肯定了他們運行的順序,item按數字從低到高的順序,經過pipeline,一般將這些數字定義在0-1000範圍內。
相關文章
相關標籤/搜索