Python網(wǎng)絡(luò)爬蟲

1. 概述

本文主要介紹網(wǎng)絡(luò)爬蟲溉知,采用的實(shí)現(xiàn)語言為Python引有,目的在于闡述網(wǎng)絡(luò)爬蟲的原理和實(shí)現(xiàn)栖秕,并且對目前常見的爬蟲技術(shù)進(jìn)行擴(kuò)展嫩与。
主要的內(nèi)容有:

  • 爬取Hi運(yùn)動網(wǎng)站實(shí)例
  • 介紹Scrapy框架蜂桶,將其項(xiàng)目轉(zhuǎn)化成Scrapy項(xiàng)目
  • 介紹反爬蟲相關(guān)
  • 對百度貼吧模擬登錄儡毕,獲取用戶關(guān)注的貼吧、自動發(fā)帖扑媚、自動簽到功能

2. 基礎(chǔ)概念

  • 爬蟲:通過一系列的腳本模仿用戶訪問web頁面腰湾,用來獲取相關(guān)數(shù)據(jù)的方式。
  • 瀏覽器:獲取遠(yuǎn)程的HTML+JS+CSS文件疆股,通過引擎渲染成可視化頁面费坊。
  • TCP/IP模型:有四個抽象的層次,描述了總體的設(shè)計大綱旬痹。物理層/實(shí)體層附井,鏈接層,網(wǎng)絡(luò)層两残,傳輸層永毅,應(yīng)用層。
  • HTTP和HTTPS:http是超文本傳輸協(xié)議人弓,信息是明文傳輸沼死;https則是具有安全性的ssl加密傳輸協(xié)議。
  • GET和POST請求:根據(jù)HTTP標(biāo)準(zhǔn)票从,HTTP請求可以使用多種請求方法漫雕。GET請求指定的頁面信息滨嘱,并返回實(shí)體主體;POST請求向指定資源提交數(shù)據(jù)進(jìn)行處理請求(例如提交表單或者上傳文件)浸间。數(shù)據(jù)被包含在請求體中太雨。POST請求可能會導(dǎo)致新的資源的建立和/或已有資源的修改。
  • Cookie:是客戶端的解決方案魁蒜,是由服務(wù)器發(fā)給客戶端的特殊信息囊扳,而這些信息以文本文件的方式存放在客戶端,然后客戶端每次向服務(wù)器發(fā)送請求的時候都會帶上這些特殊的信息兜看。
  • Session:是另一種記錄客戶狀態(tài)的機(jī)制锥咸,不同的是cookie保存在客戶端瀏覽器中,而session保存在服務(wù)器上细移〔瑁客戶端瀏覽器訪問服務(wù)器的時候,服務(wù)器把客戶端信息以某種形式記錄在服務(wù)器上弧轧。

3. 簡單爬蟲實(shí)例

下面使用Python語言實(shí)現(xiàn)一個簡單的爬蟲程序雪侥,爬取的網(wǎng)站是Hi運(yùn)動。爬取的目標(biāo)是:

  • 獲取健身動作視頻鏈接
  • 獲取健身動作相關(guān)信息精绎,包括健身部位速缨、動作要領(lǐng)等
  • 保存成數(shù)據(jù)庫

3.1 觀察頁面DOM結(jié)構(gòu)

在編寫爬蟲之前,需要先了解所爬頁面的大致DOM結(jié)構(gòu)代乃。所謂DOM就是文檔對象模型旬牲,在網(wǎng)頁中把數(shù)據(jù)組織在一個樹形結(jié)構(gòu)中,方便組織管理搁吓。通過解析這個樹形結(jié)構(gòu)原茅,獲取HTML標(biāo)簽內(nèi)部的值就能夠得到我們想要的數(shù)據(jù)。

首先打開網(wǎng)址四足俯臥撐堕仔,然后右鍵查看源碼员咽,可以看到此HTML頁面的源碼,相關(guān)數(shù)據(jù)都在此數(shù)據(jù)里贮预。按Ctrl+F搜索字符video查找一下有關(guān)視頻播放的信息贝室,發(fā)現(xiàn)在頁面的下方,<script> 標(biāo)簽有關(guān)的json數(shù)據(jù)中包含有對應(yīng)信息仿吞。

3-1.png

之后為了更好查看這段json信息滑频,使用JSON在線解析工具

3-1-2.png

完整的json數(shù)據(jù)如下:

{
  "id":"1",
  "create_time":"2016-03-29 17:30:46",
  "name":"四足俯臥撐",
  "eng_name":"",
  "description":"1、挺胸收腹唤冈,軀干與地面平行
2峡迷、雙手與肩同寬,始終保持腰背挺直,控制肘部緊貼身體兩側(cè)",
  "difficulty":"6",
  "muscle_id":"27",
  "coach_gender":"0",
  "group_id":"2",
  "category_id":"2",
  "created_from":"0",
  "status":"1",
  "ext_info":null,
  "web_mvideo_id":null,
  "web_fvideo_id":null,
  "extra_equipment_type":null,
  "equipment_ids":"3",
  "tpoints":"6",
  "equipments":[
    {
      "id":"3",
      "create_time":"2016-03-29 16:59:17",
      "name":"徒手訓(xùn)練",
      "pic":"",
      "show_type":"0",
      "equipment_group":"0",
      "sort_num":"0"
    }
  ],
  "trainingPoints":"胸部",
  "difficulty_name":"初級",
  "gender_group":{
    "male":true,
    "female":true
  },
  "category_name":"上肢",
  "muscle_name":"胸大肌",
  "otherMuscles":[
    {
      "exercise_id":"1",
      "muscle_id":"17"
    }
  ],
  "exe_explain_pic":[
    {
      "name":null,
      "url":"http://image.yy.com/ojiastoreimage/1462264660160_am__len94877.jpg",
      "desc":"1绘搞、挺胸收腹彤避,軀干與地面平行"
    },
    {
      "name":null,
      "url":"http://image.yy.com/ojiastoreimage/1462264665645_am__len98161.jpg",
      "desc":"
1、雙手與肩同寬夯辖,始終保持腰背挺直琉预,控制肘部緊貼身體兩側(cè)"
    }
  ],
  "video_id":"7033",
  "pic":"https://w2.dwstatic.com/yy/ojiastoreimage/20160429_7a5105bb4b153caaf030c490ce36d5a1.jpg",
  "gif":"https://w2.dwstatic.com/yy/ojiastorevideos/a0ced8d3263db3e6cbf774d516c5eec5.gif",
  "detail_video_id":null,
  "muscle_pic":"",
  "video_url":"https://dw-w6.dwstatic.com/49/1/1617/1838324-101-1461899919.mp4",
  "detail_video_url":null,
  "split_time":null,
  "muscle_front_img":"https://w2.dwstatic.com/yy/ojiastoreimage/1477640202013_am_",
  "muscle_back_img":"https://w2.dwstatic.com/yy/ojiastoreimage/1477640203030_am_",
  "definition_group":[
    {
      "definition":1300,
      "name":"超清",
      "url":"https://dw-w6.dwstatic.com/50/1/1617/1838324-100-1461899919.mp4",
      "length":2760,
      "size":"300458",
      "is_default":false
    },
    {
      "definition":"yuanhua",
      "name":"原畫",
      "url":"https://dw-w6.dwstatic.com/49/1/1617/1838324-101-1461899919.mp4",
      "length":2760,
      "size":"709332",
      "is_default":true
    }
  ]
}

通過這段json就可以直接獲取到我們想要的數(shù)據(jù)了:

  • video_url:視頻鏈接
  • name:動作名稱
  • difficulty_name:動作級別
  • training_points:鍛煉部位
  • muscle_name:鍛煉的肌肉
  • description:動作要領(lǐng)

3.2 Requests庫使用

當(dāng)已經(jīng)了解了所爬取頁面的DOM結(jié)構(gòu)之后,下面就開始編寫爬蟲程序蒿褂。

首先介紹Python的網(wǎng)絡(luò)操作庫——Requests圆米。
Requests 提供了HTTP很多功能,幾乎涵蓋了當(dāng)今 Web 服務(wù)的需求啄栓,比如:

  • HTTP 請求與相應(yīng)
  • 瀏覽器式的 SSL 驗(yàn)證
  • 身份認(rèn)證
  • Keep-Alive & 連接池
  • 帶持久 Cookie 的會話
  • 流下載
  • 文件分塊上傳

安裝方法pip install requests撞牢,簡單地使用如下:

>>> r = requests.get('https://www.hiyd.com/dongzuo/1/', timeout=5))
>>> r.status_code
200
>>> r.encoding
'utf-8'
>>> r.text
'<!doctype html>
<html>
<head>
    <meta charset="utf-8">... ...'

更多其他功能撼泛,請見官方文檔乒躺。

據(jù)此編寫爬蟲的聯(lián)網(wǎng)操作:

import requests
class Fitness:

    def get_info(self, url):
        r = requests.get(url, timeout=5)
        print(r.text)


if __name__ == "__main__":
    fitness = Fitness()
    fitness.get_info('https://www.hiyd.com/dongzuo/1/')

分析代碼科展,通過get請求獲取response,然后調(diào)用text屬性獲取頁面數(shù)據(jù)堪旧,我們就可以根據(jù)此數(shù)據(jù)進(jìn)行解析数焊。

3.3 BeautifulSoup庫使用

Beautiful Soup 是一個可以從HTML或XML文件中提取數(shù)據(jù)的Python庫。它能夠通過你喜歡的轉(zhuǎn)換器實(shí)現(xiàn)慣用的文檔導(dǎo)航崎场,查找,修改文檔的方式遂蛀。安裝方法pip install beautifulsoup4谭跨,簡單實(shí)用如下:

from bs4 import BeautifulSoup
import requests

r = requests.get('https://www.hiyd.com/dongzuo/1/', timeout=5)
soup = BeautifulSoup(r.text, "html.parser")
print(soup.title)
# <title>四足俯臥撐正確動作要領(lǐng)_四足俯臥撐視頻GIF圖解_Hi運(yùn)動健身網(wǎng)</title>
print(soup.a)
# <a class="o-header_logo" >
# <img src="/static/img/logo3.png?218f9b39b0457ae3"/>
# </a>
print(soup.a['href'])
# https://www.hiyd.com/
print(soup.find_all('a'))
# [<a class="o-header_logo" >
# <img src="/static/img/logo3.png?218f9b39b0457ae3"/>
# </a>, <a class="item"  target="_self">健...
print(soup.find(id='group_exercise'))
# <div class="menu-group group-tp2 group-expand" id="group_exercise">
# <div class="group-hd">
# <i></i><h3>訓(xùn)練動作</h3><em></em>
# </div>
# <div class="group-bd">
# <div class="menu-item instrument">... ...
print(soup.find_all("script"))
# [<script>
# SITE_URL = "/";
# </script>, <script src="/static/js/libs/seajs.utils.js?7c9ae9ca1b254cde"></script>, <script>
#     seajs.use(['ouj_sdk'], function(sdk) {
#         sdk.init();
#     });
# </script>, <script>... ...

更多其他功能,請見官方文檔

接著3.2中的代碼李滴,使用BeautifulSoup完成解析數(shù)據(jù)

from bs4 import BeautifulSoup
import requests
import re
import json


class Fitness:
  headers = {
      'User-Agent': 'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6'
  }
    def get_info(self, url):
        r = requests.get(url, headers=self.headers, timeout=5)
        soup = BeautifulSoup(r.text, "html.parser")
        text = str(soup.find_all("script")[-1])
        data = json.loads(re.search(r'e.init\((.+?)\);', text).group(1))
        print(data)

if __name__ == "__main__":
    fitness = Fitness()
    fitness.get_info('https://www.hiyd.com/dongzuo/1/')

其中螃宙,data = json.loads(re.search(r'e.init\((.+?)\);', text).group(1))含義是,將獲取到的數(shù)據(jù)通過正則表達(dá)式取出中間的json文本數(shù)據(jù)所坯,再通過json.loads方法將json數(shù)據(jù)轉(zhuǎn)換為python的dict數(shù)據(jù)谆扎。得到的data數(shù)據(jù)為:

{'muscle_front_img': 'https://w2.dwstatic.com/yy/ojiastoreimage/1477640202013_am_', 'coach_gender': '0', 'extra_equipment_type': None, 'created_from': '0', 'status': '1', 'difficulty_name': '初級', 'tpoints': '6', 'detail_video_url': None, 'muscle_id': '27', 'trainingPoints': '胸部', 'definition_group': [{'is_default': False, 'url': 'https://dw-w6.dwstatic.com/50/1/1617/1838324-100-1461899919.mp4', 'length': 2760, 'definition': 1300, 'name': '超清', 'size': '300458'}, {'is_default': True, 'url': 'https://dw-w6.dwstatic.com/49/1/1617/1838324-101-1461899919.mp4', 'length': 2760, 'definition': 'yuanhua', 'name': '原畫', 'size': '709332'}], 'description': '1、挺胸收腹芹助,軀干與地面平行\(zhòng)r\n2堂湖、雙手與肩同寬,始終保持腰背挺直状土,控制肘部緊貼身體兩側(cè)', 'video_url': 'https://dw-w6.dwstatic.com/49/1/1617/1838324-101-1461899919.mp4', 'muscle_pic': '', 'video_id': '7033', 'split_time': None, 'eng_name': '', 'otherMuscles': [{'muscle_id': '17', 'exercise_id': '1'}], 'equipments': [{'pic': '', 'show_type': '0', 'create_time': '2016-03-29 16:59:17', 'name': '徒手訓(xùn)練', 'sort_num': '0', 'equipment_group': '0', 'id': '3'}], 'web_mvideo_id': None, 'ext_info': None, 'muscle_back_img': 'https://w2.dwstatic.com/yy/ojiastoreimage/1477640203030_am_', 'gender_group': {'male': True, 'female': True}, 'gif': 'https://w2.dwstatic.com/yy/ojiastorevideos/a0ced8d3263db3e6cbf774d516c5eec5.gif', 'category_name': '上肢', 'difficulty': '6', 'category_id': '2', 'group_id': '2', 'pic': 'https://w2.dwstatic.com/yy/ojiastoreimage/20160429_7a5105bb4b153caaf030c490ce36d5a1.jpg', 'web_fvideo_id': None, 'create_time': '2016-03-29 17:30:46', 'muscle_name': '胸大肌', 'name': '四足俯臥撐', 'id': '1', 'exe_explain_pic': [{'name': None, 'url': 'http://image.yy.com/ojiastoreimage/1462264660160_am__len94877.jpg', 'desc': '1无蜂、挺胸收腹,軀干與地面平行'}, {'name': None, 'url': 'http://image.yy.com/ojiastoreimage/1462264665645_am__len98161.jpg', 'desc': '\r1蒙谓、雙手與肩同寬斥季,始終保持腰背挺直,控制肘部緊貼身體兩側(cè)'}], 'detail_video_id': None, 'equipment_ids': '3'}

之后,我們就可以通過這個dict數(shù)據(jù)取出想要的數(shù)據(jù)了酣倾。

3.4 頁面跳轉(zhuǎn)

以上實(shí)現(xiàn)的功能是取出單一頁面的數(shù)據(jù)舵揭,但是可以看到Hi運(yùn)動動作庫中還有很多個視頻頁面需要提取,此時就需要為爬蟲程序添加頁面跳轉(zhuǎn)的功能躁锡。
我們再繼續(xù)觀察'https://www.hiyd.com/dongzuo/'頁面午绳,可以發(fā)現(xiàn)源碼中帶有<div class="cont">的屬于當(dāng)前頁所有動作的詳細(xì)鏈接,獲取這些鏈接傳給get_info方法就像之前實(shí)現(xiàn)的邏輯獲取視頻詳細(xì)信息了稚铣。
實(shí)現(xiàn)代碼:

def get_pages(self, url):
    r = requests.get(url, headers=self.headers, timeout=5)
    soup = BeautifulSoup(r.text, "html.parser")
    for x in soup.find_all("div", class_="cont"):
        print(x.a.get('href'))

其中箱叁,soup.find_all("div", class_="cont")是取得所有帶有class="cont"的div標(biāo)簽(因?yàn)閏lass是python自有的,所以BeautifulSoup用class_進(jìn)行替代)惕医,然后將得到的list結(jié)果進(jìn)行迭代耕漱,獲取子標(biāo)簽a的href屬性值。得到的結(jié)果:

/dongzuo/1/
/dongzuo/2/
/dongzuo/3/
/dongzuo/4/
/dongzuo/5/
/dongzuo/6/
/dongzuo/7/
/dongzuo/8/
/dongzuo/9/
/dongzuo/10/
/dongzuo/11/
/dongzuo/12/
/dongzuo/13/
/dongzuo/14/
/dongzuo/15/
/dongzuo/16/
/dongzuo/17/
/dongzuo/18/
/dongzuo/19/
/dongzuo/20/

上面實(shí)現(xiàn)的是獲取動作庫中第一頁的所有視頻鏈接抬伺,但是還需要提取其余80頁數(shù)據(jù)螟够。分析動作庫頁面源碼,可以看到<a href="/dongzuo/?page=2" onclick="_pageClick('next')" rel="next" title="下一頁">下一頁</a>顯示了當(dāng)存在下一頁時峡钓,下一頁的頁面鏈接數(shù)據(jù)妓笙,而在第80頁時沒有此數(shù)據(jù)。據(jù)此能岩,我們就可以獲取到下一頁的鏈接地址寞宫。

host = 'https://www.hiyd.com'
def get_pages(self, url):
    r = requests.get(url, headers=self.headers, timeout=5)
    soup = BeautifulSoup(r.text, "html.parser")
    for x in soup.find_all("div", class_="cont"):
        self.get_info(self.host + x.a.get('href'))
    next_page_url = str(soup.find("a", rel="next").get('href'))
    if next_page_url is not None and next_page_url != "/dongzuo/?page=3":
        self.get_pages(self.host + next_page_url)

獲取的next_page_url如果不為None就遞歸調(diào)用get_pages方法,此處加的next_page_url != "/dongzuo/?page=3"是加一個頁數(shù)限制拉鹃,避免一下取80頁辈赋。
至此,頁面跳轉(zhuǎn)和頁面數(shù)據(jù)解析的功能已經(jīng)全部實(shí)現(xiàn)完成膏燕,下面就是對數(shù)據(jù)進(jìn)行存儲钥屈。

3.5 保存數(shù)據(jù)

下面介紹兩種保存方式,一是保存視頻文件坝辫,二是將信息保存到數(shù)據(jù)庫中篷就。

3.5.1 保存視頻文件

requests庫支持流下載,查看文檔近忙,原始響應(yīng)內(nèi)容 章節(jié)竭业。

3-5-1.png

根據(jù)文檔,實(shí)現(xiàn)視頻下載邏輯:

def download(self, name, url):
    if os.path.exists("./video") is False:
        os.makedirs("./video")
    with requests.get(url, stream=True) as response:
        with open("./video/" + name + ".mp4", "wb") as file:
            for data in response.iter_content(chunk_size=1024):
                file.write(data)

首先在當(dāng)前目錄下創(chuàng)建video文件夾及舍,然后用with語句調(diào)用requests.get(url, stream=True)永品,(with語句的作用是保證response會調(diào)用close方法。在請求中把 stream 設(shè)為 True击纬,Requests 無法將連接釋放回連接池鼎姐,除非你消耗了所有的數(shù)據(jù),或者調(diào)用了 Response.close。)炕桨。接著調(diào)用open方法設(shè)置二進(jìn)制寫入方式饭尝,將response的迭代數(shù)據(jù)寫入到文件中。

3.5.2 保存數(shù)據(jù)庫

數(shù)據(jù)庫選擇Python自帶比較簡單的Sqlite數(shù)據(jù)庫献宫,無需安裝驅(qū)動即可使用钥平。相關(guān)使用方法,請見SQLite - Python.

下面是數(shù)據(jù)庫相關(guān)操作的實(shí)現(xiàn)邏輯:

def __init__(self):
    self.conn = sqlite3.connect('fitness_test.db')
    self.create_db()

def create_db(self):
    self.conn.execute("CREATE TABLE IF NOT EXISTS fitness (id INTEGER PRIMARY KEY, "
                      "name TEXT, "
                      "muscle_name TEXT, "
                      "description TEXT, "
                      "video_url TEXT);")

def save_db(self, data):
    self.conn.execute("INSERT INTO fitness (name, muscle_name, description, video_url) VALUES(?, ?, ?, ?)", data)

def close_db(self):
    self.conn.commit()
    self.conn.close()

__init__初始化方法中連接數(shù)據(jù)庫姊途,如果沒有會在當(dāng)前目錄下創(chuàng)建數(shù)據(jù)庫涉瘾,然后創(chuàng)建對應(yīng)的表結(jié)構(gòu)。save_db方法捷兰,接收一個list參數(shù)立叛,對應(yīng)sql插入語句中的參數(shù)。close_db方法贡茅,提交數(shù)據(jù)庫事務(wù)秘蛇,并關(guān)閉數(shù)據(jù)庫。

3.6 完整代碼

至此顶考,Hi運(yùn)動爬蟲demo的功能已經(jīng)開發(fā)完成赁还,下面是完整的代碼:

import os

from bs4 import BeautifulSoup
import requests
import re
import sqlite3
import json


class Fitness:
    host = 'https://www.hiyd.com'

    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6'
    }

    def __init__(self):
        self.conn = sqlite3.connect('fitness_test.db')
        self.create_db()

    def get_pages(self, url):
        r = requests.get(url, headers=self.headers, timeout=5)
        soup = BeautifulSoup(r.text, "html.parser")
        for x in soup.find_all("div", class_="cont"):
            self.get_info(self.host + x.a.get('href'))
        next_page_url = str(soup.find("a", rel="next").get('href'))
        if next_page_url is not None and next_page_url != "/dongzuo/?page=3":
            self.get_pages(self.host + next_page_url)
        else:
            self.close_db()

    def get_info(self, url):
        r = requests.get(url, headers=self.headers, timeout=5)
        soup = BeautifulSoup(r.text, "html.parser")
        text = str(soup.find_all("script")[-1])
        data = json.loads(re.search(r'e.init\((.+?)\);', text).group(1))
        self.download(data['name'], data['video_url'])
        self.save_db([data['name'], data['muscle_name'], data['description'], data['video_url']])
        print("done " + data['name'])

    def download(self, name, url):
        if os.path.exists("./video") is False:
            os.makedirs("./video")
        with requests.get(url, stream=True) as response:
            with open("./video/" + name + ".mp4", "wb") as file:
                for data in response.iter_content(chunk_size=1024):
                    file.write(data)

    def create_db(self):
        self.conn.execute("CREATE TABLE IF NOT EXISTS fitness (id INTEGER PRIMARY KEY, "
                          "name TEXT, "
                          "muscle_name TEXT, "
                          "description TEXT, "
                          "video_url TEXT);")

    def save_db(self, data):
        self.conn.execute("INSERT INTO fitness (name, muscle_name, description, video_url) VALUES(?, ?, ?, ?)", data)

    def close_db(self):
        self.conn.commit()
        self.conn.close()


if __name__ == "__main__":
    fitness = Fitness()
    fitness.get_pages('https://www.hiyd.com/dongzuo/')

4. 拓展

從上面的章節(jié)來看,可能會覺得爬蟲程序很簡單驹沿,但是這只是對于個人學(xué)習(xí)的demo艘策,到真正的工程化項(xiàng)目來說還有很大的差距。困難主要在于:

  • 數(shù)據(jù)量大:爬取大型網(wǎng)站時渊季,可能會產(chǎn)生億級的數(shù)據(jù)朋蔫,如何高效存儲、分析數(shù)據(jù)梭域,如何將數(shù)據(jù)可視化等問題
  • 效率:對于高效率地爬取大量數(shù)據(jù),如何實(shí)現(xiàn)構(gòu)建高并發(fā)搅轿、分布式爬蟲系統(tǒng)
  • 反爬蟲策略:目前很多主流的網(wǎng)站都會有反爬蟲策略病涨,防止大量的爬蟲對運(yùn)維造成壓力。如何處理IP被封璧坟,如何處理驗(yàn)證碼等問題

可以看到既穆,工程化的爬蟲程序需要解決很多的實(shí)際問題,這些實(shí)際問題往往很難解決雀鹃。所以入門爬蟲的門檻很低幻工,但是成長曲線很陡峭。接下來黎茎,介紹一些常見的爬蟲進(jìn)階知識囊颅。

4.1 Scrapy框架

Scrapy是一個為了爬取網(wǎng)站數(shù)據(jù),提取結(jié)構(gòu)性數(shù)據(jù)而編寫的應(yīng)用框架。 可以應(yīng)用在包括數(shù)據(jù)挖掘踢代,信息處理或存儲歷史數(shù)據(jù)等一系列的程序中盲憎。安裝方式pip install Scrapy(需要翻墻,并且根據(jù)提示安裝依賴庫)胳挎,或者通過Anaconda來便捷安裝scrapy饼疙。

下面將Hi運(yùn)動爬蟲程序轉(zhuǎn)換成Scrapy項(xiàng)目。

4.1.1 創(chuàng)建項(xiàng)目

進(jìn)入打算存儲代碼的目錄中慕爬,運(yùn)行下列命令
scrapy startproject fitness
該命令將會創(chuàng)建包含下列內(nèi)容的SpiderExercise 目錄:

SpiderExercise/
    scrapy.cfg
    fitness/
        __init__.py
        items.py
        pipelines.py
        settings.py
        spiders/
            __init__.py
            ...

這些文件分別是:

  • scrapy.cfg: 項(xiàng)目的配置文件
  • fitness/: 該項(xiàng)目的python模塊窑眯。之后您將在此加入代碼。
  • fitness/items.py: 項(xiàng)目中的item文件.
  • fitness/pipelines.py: 項(xiàng)目中的pipelines文件.
  • fitness/settings.py: 項(xiàng)目的設(shè)置文件.
  • fitness/spiders/: 放置spider代碼的目錄.

4.1.2 定義Item

Item 是保存爬取到的數(shù)據(jù)的容器医窿;其使用方法和python字典類似磅甩,并且提供了額外保護(hù)機(jī)制來避免拼寫錯誤導(dǎo)致的未定義字段錯誤。類似在ORM中做的一樣留搔,可以通過創(chuàng)建一個 scrapy.Item 類更胖,并且定義類型為 scrapy.Field 的類屬性來定義一個Item。

首先根據(jù)需要從http://www.hiyd.com/獲取到的數(shù)據(jù)對item進(jìn)行建模隔显。 我們需要從hiyd中獲取肌肉id却妨、動作庫名稱等字段。對此括眠,在item中定義相應(yīng)的字段彪标。編輯 fitness目錄中的 items.py 文件:

from scrapy.item import Item, Field


class FitnessItem(Item):
    muscle_id = Field()
    name = Field()
    difficulty_name = Field()
    training_points = Field()
    category_name = Field()
    muscle_name = Field()
    equipments = Field()
    description = Field()
    video = Field()
    gif = Field()
    muscle_pic = Field()
    muscle_front_img = Field()
    muscle_back_img = Field()
    other_muscles = Field()

一開始這看起來可能有點(diǎn)復(fù)雜,但是通過定義item掷豺,可以很方便的使用Scrapy的其他方法捞烟。而這些方法需要知道item的定義。

4.1.3 提取Item

從網(wǎng)頁中提取數(shù)據(jù)有很多方法当船。Scrapy使用了一種基于XPathCSS 表達(dá)式機(jī)制题画。
這里給出XPath表達(dá)式的例子及對應(yīng)的含義:

  • /html/head/title: 選擇HTML文檔中<head>標(biāo)簽內(nèi)的<title> 元素
  • /html/head/title/text(): 選擇上面提到的<title>元素的文字
  • //td: 選擇所有的 <td> 元素
  • //div[@class="mine"]: 選擇所有具有 class="mine" 屬性的 div 元素

上邊僅僅是幾個簡單的XPath例子,XPath實(shí)際上要比這遠(yuǎn)遠(yuǎn)強(qiáng)大的多德频。具體請參考XPath 教程

為了配合XPath苍息,Scrapy除了提供了 Selector 之外,還提供了方法來避免每次從response中提取數(shù)據(jù)時生成selector的麻煩壹置。

Selector有四個基本的方法:

  • xpath(): 傳入xpath表達(dá)式竞思,返回該表達(dá)式所對應(yīng)的所有節(jié)點(diǎn)的selector list列表 。
  • css(): 傳入CSS表達(dá)式钞护,返回該表達(dá)式所對應(yīng)的所有節(jié)點(diǎn)的selector list列表.
  • extract(): 序列化該節(jié)點(diǎn)為unicode字符串并返回list盖喷。
  • re(): 根據(jù)傳入的正則表達(dá)式對數(shù)據(jù)進(jìn)行提取,返回unicode字符串list列表难咕。

下面就通過XPath提取Hi運(yùn)動網(wǎng)站提取數(shù)據(jù):

  • 提取視頻頁面數(shù)據(jù):text = response.xpath('//script').extract()[-1]
  • 提取動作庫每頁的鏈接:
for href in response.xpath('//div[@class="cont"]/a[@target="_blank"]/@href').extract():
    url = response.urljoin(href)
  • 提取下一頁的鏈接:next_page_url = response.xpath('//a[@rel="next"]/@href').extract_first()

4.1.4 編寫爬蟲

Spider是用戶編寫用于從單個網(wǎng)站(或者一些網(wǎng)站)爬取數(shù)據(jù)的類课梳。其包含了一個用于下載的初始URL距辆,如何跟進(jìn)網(wǎng)頁中的鏈接以及如何分析頁面中的內(nèi)容, 提取生成 item 的方法惦界。
為了創(chuàng)建一個Spider挑格,必須繼承 scrapy.Spider 類,且定義以下三個屬性:

  • name: 用于區(qū)別Spider沾歪。 該名字必須是唯一的漂彤,您不可以為不同的Spider設(shè)定相同的名字。
  • start_urls: 包含了Spider在啟動時進(jìn)行爬取的url列表灾搏。 因此挫望,第一個被獲取到的頁面將是其中之一。 后續(xù)的URL則從初始的URL獲取到的數(shù)據(jù)中提取狂窑。
  • parse() 是spider的一個方法媳板。 被調(diào)用時,每個初始URL完成下載后生成的 Response 對象將會作為唯一的參數(shù)傳遞給該函數(shù)泉哈。 該方法負(fù)責(zé)解析返回的數(shù)據(jù)(response data)蛉幸,提取數(shù)據(jù)(生成item)以及生成需要進(jìn)一步處理的URL的 Request 對象。

以下為我們的第一個Spider代碼丛晦,保存在fitness/spiders 目錄下的 jirou.py 文件中:

import json

import scrapy

from fitness.items import FitnessItem


class DmozSpider(scrapy.Spider):
    name = "fitness"
    allowed_domains = ["hiyd.com"]
    start_urls = [
        "http://www.hiyd.com/dongzuo"
    ]

    def parse(self, response):
        for href in response.xpath('//div[@class="cont"]/a[@target="_blank"]/@href').extract():
            url = response.urljoin(href)
            yield scrapy.Request(url, callback=self.parse_info)
        next_page_url = response.xpath('//a[@rel="next"]/@href').extract_first()
        if next_page_url is not None and next_page_url != "/dongzuo/?page=2":
            yield scrapy.Request(response.urljoin(next_page_url))

    def parse_info(self, response):
        item = FitnessItem()
        text = response.xpath('//script').extract()[-1]
        data_text = text.split("e.init(")[1].split(");")[0]
        json_text = json.loads(data_text)
        other_muscle = json_text["otherMuscles"]
        temp = []
        if len(other_muscle) != 0:
            for x in other_muscle:
                temp.append(self.change_muscle_id(x["muscle_id"]))

        item['name'] = json_text["name"]
        item['difficulty_name'] = json_text["difficulty_name"]
        item['training_points'] = json_text["trainingPoints"]
        item['category_name'] = json_text["category_name"]
        item['muscle_name'] = json_text["muscle_name"]
        item['muscle_id'] = self.change_muscle_id(json_text["muscle_id"])
        item['other_muscles'] = ",".join(temp)
        item['equipments'] = ("徒手訓(xùn)練" if json_text["equipments"][0] is None else json_text["equipments"][0]["name"])
        item['description'] = json_text["description"]
        item['video'] = json_text["video_url"]
        item['gif'] = json_text["gif"]
        item['muscle_pic'] = json_text["muscle_pic"]
        item['muscle_front_img'] = json_text["muscle_front_img"]
        item['muscle_back_img'] = json_text["muscle_back_img"]
        item['file_urls'] = [json_text["video_url"]]
        yield item

    def change_muscle_id(self, muscle_id):
        """
        網(wǎng)站數(shù)據(jù)中奕纫,有些肌肉的id有錯誤,此方法是把錯誤的肌肉id糾正烫沙。
        :param muscle_id:
        :return:
        """
        unknown = ["27", "28", "29", "30", "31", "32", "33", "34", "35", "36", "37"]
        known = ["9", "21", "12", "19", "10", "16", "23", "20", "20", "16", "16"]
        if muscle_id in unknown:
            muscle_id = known[unknown.index(muscle_id)]
        return muscle_id

可以看到parse方法對每一頁動作庫的鏈接進(jìn)行了遞歸處理匹层,并且提取了下一頁的鏈接,yield返回了一個函數(shù)生成器锌蓄,方便Scrapy框架內(nèi)部進(jìn)行迭代處理升筏。然后調(diào)用parse_info方法解析每個動作的詳細(xì)信息。

Item 對象是自定義的python字典瘸爽∧茫可以使用標(biāo)準(zhǔn)的字典語法來獲取到其每個字段的值。(字段即是我們之前用Field賦值的屬性)剪决,一般來說灵汪,Spider將會將爬取到的數(shù)據(jù)以 Item 對象返回。

4.1.5 保存爬取到的數(shù)據(jù)

采用 JSON 格式對爬取的數(shù)據(jù)進(jìn)行序列化昼捍,生成 items.json 文件识虚。編寫fitness/pipelines.py肢扯,將之前返回的item數(shù)據(jù)輸入到管道中妒茬,進(jìn)行保存。


from scrapy.pipelines.files import FilesPipeline
from urllib.parse import urlparse
from os.path import basename, dirname, join
from scrapy.conf import settings


class FitnessPipeline(object):

    def __init__(self):
        self.file = open('jirou.json', 'w', encoding='utf-8')

    def process_item(self, item, spider):
        line = json.dumps(dict(item), ensure_ascii=False) + '\n'
        self.file.write(line)
        return item

    def spider_closed(self, spider):
        self.file.close()

可以看到蔚晨,重寫了process_item方法乍钻,將傳入的參數(shù)item進(jìn)行json格式轉(zhuǎn)化肛循,并且寫入到文件中。重寫spider_closed方法關(guān)閉文件流银择。

之后將管道配置到settings.py并且寫入到文件中:

BOT_NAME = 'fitness'
BOT_VERSION = '1.0'

SPIDER_MODULES = ['fitness.spiders']
NEWSPIDER_MODULE = 'fitness.spiders'
#USER_AGENT = '%s/%s' % (BOT_NAME, BOT_VERSION)
USER_AGENT = 'User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 ' \
             '(KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36'
ITEM_PIPELINES = {
    'fitness.pipelines.FitnessPipeline': 300
}

4.1.6 開始爬取

執(zhí)行命令scrapy crawl fitness, 可以看到生成了包含數(shù)據(jù)的json文件多糠。

更多Scrapy框架功能,請見Scrapy文檔

4.2 分布式爬蟲

所謂的分布式爬蟲浩考,就是多臺機(jī)器合作進(jìn)行爬蟲工作夹孔,提高工作效率。
分布式爬蟲需要考慮的問題有:

  • 如何從一個統(tǒng)一的接口獲取待抓取的URL析孽?
  • 如何保證多臺機(jī)器之間的排重操作搭伤?即保證不會出現(xiàn)多臺機(jī)器同時抓取同一個URL。

Redis數(shù)據(jù)庫是一種key-value數(shù)據(jù)庫袜瞬,所有操作都是原子性的怜俐,意思就是要么成功執(zhí)行要么失敗完全不執(zhí)行,單個操作是原子性的邓尤。而且Redis中自帶的“消息隊(duì)列”拍鲤,方便來解決分布式爬蟲的任務(wù)分配問題。

架構(gòu)設(shè)計如下:

4-1.png

在Master端跑一個程序去生成所有任務(wù)(Request/url/ID)汞扎。Master端負(fù)責(zé)的是生產(chǎn)任務(wù)季稳,并把任務(wù)去重、加入到待爬隊(duì)列佩捞。Slaver只管從Master端拿任務(wù)去爬绞幌。
其中:

  • Master:跑一個程序去對應(yīng)的網(wǎng)站上生成所有任務(wù)(Request/url/ID),并且把任務(wù)存入Redis數(shù)據(jù)庫中一忱,形成一個任務(wù)隊(duì)列莲蜘,同時去重任務(wù)。對應(yīng)到Hi運(yùn)動爬蟲程序來說帘营,相當(dāng)于在一臺機(jī)器中票渠,將動作庫頁面的所有80頁數(shù)據(jù)提取出來所有動作庫視頻的鏈接,并構(gòu)造一個任務(wù)隊(duì)列芬迄。
  • Slaver:對應(yīng)各個具體爬蟲工作的機(jī)器问顷,只管從Master端拿任務(wù)去爬,并且將爬取的數(shù)據(jù)保存到一個數(shù)據(jù)庫中禀梳。對應(yīng)到Hi運(yùn)動爬蟲程序來說杜窄,相當(dāng)于從Redis任務(wù)隊(duì)列中取出一條視頻頁面鏈接進(jìn)行詳細(xì)信息的爬取,并存入數(shù)據(jù)庫中算途。

具體實(shí)現(xiàn)比較復(fù)雜塞耕,感興趣的可以通過Scrapy+Redis的方式進(jìn)一步實(shí)現(xiàn)。

4.3 反爬蟲策略

對于大型網(wǎng)站來說嘴瓤,如果有很多爬蟲應(yīng)用在短時間內(nèi)大規(guī)模爬取的話扫外,會對這個網(wǎng)站系統(tǒng)的運(yùn)維造成很大的壓力莉钙。而且有些網(wǎng)站的信息是不希望被他人爬取的,所以各個網(wǎng)站都會有自己的一套反爬策略筛谚。常見的反爬策略有:

  • 封IP:當(dāng)某個IP地址短時間內(nèi)大量訪問磁玉,會被封禁IP地址。解決辦法是通過使用大量的代理IP地址驾讲,或者將爬取的間隔時間進(jìn)行隨機(jī)數(shù)調(diào)整蚊伞。
  • 驗(yàn)證碼:提示用驗(yàn)證碼操作。解決辦法吮铭,通過獲取所有驗(yàn)此網(wǎng)站證碼信息厚柳,進(jìn)行神經(jīng)網(wǎng)絡(luò)的分析,或者使用第三方打碼平臺沐兵。
  • Ajax動態(tài)請求:工作原理是從網(wǎng)頁的 url 加載網(wǎng)頁的源代碼之后别垮,會在瀏覽器里執(zhí)行JavaScript程序。這些程序會加載出更多的內(nèi)容扎谎,并把這些內(nèi)容傳輸?shù)骄W(wǎng)頁中碳想。解決辦法,用Python模仿JavaScript中的邏輯獲得原始的數(shù)據(jù)毁靶,或者使用PhantomJS+Selenium來模擬瀏覽器加載js胧奔。

爬蟲與反爬蟲是一個斗智斗勇的過程,每個網(wǎng)站的反爬策略都會有所不同预吆,需要對其具體情況具體分析龙填。

4.4 模擬登錄

有些網(wǎng)站會強(qiáng)制登錄才能查看一下信息,此時就需要爬蟲程序去模擬用戶登錄拐叉。下面我們嘗試模擬登錄百度貼吧獲取當(dāng)前用戶關(guān)注的貼吧列表岩遗。

首先打開百度貼吧,然后輸入用戶名和密碼登錄凤瘦。然后打開瀏覽器的開發(fā)者模式F12宿礁,轉(zhuǎn)到network選項(xiàng)卡。在左邊的Name一欄找到當(dāng)前的網(wǎng)址蔬芥,選擇右邊的Headers選項(xiàng)卡梆靖,查看Request Headers,這里包含了該網(wǎng)站頒發(fā)給瀏覽器的cookie笔诵。最好運(yùn)行你的程序前再登錄返吻。如果太早登錄,或是把瀏覽器關(guān)了乎婿,很可能復(fù)制的那個cookie就過期無效了测僵。
cookie是保存在發(fā)起請求的客戶端中,服務(wù)器利用cookie來區(qū)分不同的客戶端次酌。因?yàn)閔ttp是一種無狀態(tài)的連接恨课,當(dāng)服務(wù)器一下子收到好幾個請求時,是無法判斷出哪些請求是同一個客戶端發(fā)起的岳服。而“訪問登錄后才能看到的頁面”這一行為剂公,恰恰需要客戶端向服務(wù)器證明:“我是剛才登錄過的那個客戶端”。于是就需要cookie來標(biāo)識客戶端的身份吊宋,以存儲它的信息(如登錄狀態(tài))纲辽。當(dāng)然,這也意味著璃搜,只要得到了別的客戶端的cookie拖吼,我們就可以假冒成它來和服務(wù)器對話。我們先用瀏覽器登錄这吻,然后使用開發(fā)者工具查看cookie吊档。接著在程序中攜帶該cookie向網(wǎng)站發(fā)送請求,就能讓你的程序假扮成剛才登錄的那個瀏覽器唾糯,得到只有登錄后才能看到的頁面怠硼。

4-4-1.png

然后我們在源代碼中找到進(jìn)入“我的貼吧”鏈接,<a class="media_left" style="" href="/home/main?un=fobkbmdo&fr=index" target="_blank">移怯,里面的href屬性是跳轉(zhuǎn)鏈接香璃,fobkbmdo是我的貼吧用戶名。然后分析一下在“我的貼吧”頁面的源碼舟误,

</span>愛逛的吧</h1><div class="clearfix u-f-wrap" id="forum_group_wrap">         <a data-fid="59099" target="_blank" locate="like_forums#ihome_v1" href="/f?kw=%E6%9D%8E%E6%AF%85&fr=home"         class="u-f-item unsign"><span>李毅</span><span class="forum_level lv2"></span></a>         <a data-fid="407248" target="_blank" locate="like_forums#ihome_v1" href="/f?kw=%E5%8D%95%E6%9C%BA%E6%B8%B8%E6%88%8F&fr=home"         class="u-f-item unsign"><span>單機(jī)游戲</span><span class="forum_level lv1"></span></a>         <a data-fid="113893" target="_blank" locate="like_forums#ihome_v1" href="/f?kw=%E6%98%BE%E5%8D%A1&fr=home"         class="u-f-item unsign"><span>顯卡</span><span class="forum_level lv1"></span></a>         <a data-fid="543521" target="_blank" locate="like_forums#ihome_v1" href="/f?kw=%E6%8E%A8%E7%90%86&fr=home"         class="u-f-item unsign"><span>推理</span><span class="forum_level lv1"></span></a>         <a data-fid="825" target="_blank" locate="like_forums#ihome_v1" href="/f?kw=%E8%80%83%E7%A0%94&fr=home"         class="u-f-item unsign"><span>考研</span>

可以根據(jù)此提取出相關(guān)的信息葡秒,實(shí)現(xiàn)代碼如下:

import requests
from bs4 import BeautifulSoup

url = 'http://tieba.baidu.com/home/main?un=fobkbmdo&fr=index'

cookie_str = '填充自己的cookie數(shù)據(jù)'
cookies = {}
for line in cookie_str.split(';'):
    key, value = line.split('=', 1)
    cookies[key] = value

headers = {
    'User-agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36'}

resp = requests.get(url, headers=headers, cookies=cookies)
soup = BeautifulSoup(str(resp.content.decode('utf-8')), "html.parser")
for x in soup.find_all("a", class_="u-f-item unsign"):
    print(x.span.text)

4.5 模擬請求

下面介紹通過爬蟲模擬網(wǎng)絡(luò)請求,以百度貼吧自動發(fā)帖為例嵌溢。

首先選擇進(jìn)入一個貼吧眯牧,選擇一個帖子,然后打開F12開發(fā)者模式赖草,轉(zhuǎn)到network選項(xiàng)卡炸站。手動發(fā)表回復(fù),查看network變化:

4-5-1.png

發(fā)現(xiàn)是多了一個add請求疚顷,點(diǎn)擊查看發(fā)現(xiàn)具體的聯(lián)網(wǎng)請求如下:

Request URL: https://tieba.baidu.com/f/commit/post/add
Request Method: POST
Status Code: 200 OK
Remote Address: 127.0.0.1:50578
Referrer Policy: no-referrer-when-downgrade

可以發(fā)現(xiàn)“發(fā)表”功能其實(shí)是發(fā)送了https://tieba.baidu.com/f/commit/post/addPOST請求旱易。接著查看一下請求的數(shù)據(jù):

4-5-2.png

上面這些數(shù)據(jù)就是我們需要用代碼模擬的數(shù)據(jù),其中我們只需要關(guān)心修改下面的數(shù)據(jù)即可:

  • kw:貼吧名稱
  • fid:帖子id
  • tid:貼吧id
  • content:發(fā)表內(nèi)容
  • floor_num:帖子層數(shù)

下面我們使用代碼來模擬發(fā)表的功能:

import requests
from bs4 import BeautifulSoup
from selenium import webdriver

url_send = 'https://tieba.baidu.com/f/commit/post/add'

cookie_str = '輸入你的登錄cookie'
cookies = {}
for line in cookie_str.split(';'):
    key, value = line.split('=', 1)
    cookies[key] = value

headers = {
    'User-agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36'}


def send_posts():
    data = str2dic('ie=utf-8&kw=%E5%8D%95%E6%9C%BA%E6%B8%B8%E6%88%8F&fid=407248&tid=5649760137&vcode_md5=&floor_num=23&rich_text=1&tbs=f0f598aecd004a1c1524122217&content=%5Bbr%5D%E9%AD%94%E5%85%BD%E4%BA%89%E9%9C%B8&basilisk=1&files=%5B%5D&mouse_pwd=99%2C99%2C97%2C121%2C98%2C103%2C96%2C98%2C92%2C100%2C121%2C101%2C121%2C100%2C121%2C101%2C121%2C100%2C121%2C101%2C121%2C100%2C121%2C101%2C121%2C100%2C121%2C101%2C92%2C103%2C102%2C99%2C109%2C98%2C92%2C100%2C108%2C103%2C101%2C121%2C100%2C101%2C109%2C101%2C15241222546850&mouse_pwd_t=1524122254685&mouse_pwd_isclick=0&__type__=reply&_BSK=JVwSUGcLBF83AFZzQztEElBFCwIWaVcbHD5WBH8Vd2BpAHxNUnpmR1VVPDsXSAZLKBx9Dl42WSstG01BMAAGEWc4XCUIIBpRRVJrZ3oNIl5QKQhRNhUIBHEGfl4IaRMyIgVpfkRsWhklTQhRHBtRM3FLUlRrAhILbT57CywAWhAGDi4yemIQVFUiTgYGXTQ%2FPFV%2FWQZndFNGAG9iShwSWGl7PlIfIlFoa01WTXYHBgdnFAF9W2xHAF5XaXU0FFcNHn9XF3IFc2FgHHIYAGt%2BXRNSMSQBAQgHegplFExgCXBuS1BSaRNUGmdPEXpWDlMCWBMnJH8XRxINDEIUd1N2NmQJaA5WKiBNRQc8ZgccH1t9GW0GTGEPYmxMRFQBEwgJIEQRZUF%2BRgFdV3BmawlXWQxvXQYxRzM1fRIxXhFzZExMAW17Rl0ZS3MIOVUSI11rfAlVQX8TVEQ2AX46Ej8XVw9LJzsvV1lRUS4SVWlWKj8iVXwJQSgpGAYfLjIISwYeIEY7Wwl8SCYsGw8XaV5UTisQQXMVIwYcBgIrMC5NWVRSIhRDIRkqPzJRJAZcJ2gZGlAoOgFDXkUmWjZTFz4UKT8TBE8tWFdfKgdKcw0jFVEeDio5OEQHG1MoCVMnVDR8IVUiHFwnJREXUi97F05YBiVEPVUMIxprfA5TQX8Tc0IrRgF9TW4CAUhdZzEvSxZDVyIJBjFaFSQjWT4IG2BkBlVoMzYQRFwMaUswUBsNGDp8UkMKdBMeCzEHRjpNbgUCSF1lZmMXRRscIFYEfxckMSJZPAZAIhscOUVtPQMPBks9Gn0OXmENdWpPU1F3BhwHZxkBfVtsAkIfAml1PhRXDRwDMmoJF2pyPQFyVREzLFA2fX97Rl8bS3MKOUEQM0wuMRBBESRfQEQoXRp%2FGmwtXgsTLCE%2FBRZYWig6BjgXanImAXJVEQcRMTkRcXUHHAhTaVwtQRst')
    data['kw'] = '單機(jī)游戲'
    # 當(dāng)前層數(shù) - 1
    data['floor_num'] = 23
    data['content'] = '星際'
    data['title'] = '單機(jī)游戲'
    resp = requests.post(url_send, headers=headers, cookies=cookies, data=data)
    print(resp.text)


def str2dic(text):
    idic = {}
    ilist = text.split('&')
    for item in ilist:
        name, value = item.split('=', 1)
        idic[name] = value
    return idic

send_posts()

當(dāng)輸出的內(nèi)容為{"no":0,"err_code":0,"error":null,"data":{"autoMsg":"","fid":407248,"fname":"\u5355\u673a\u6e38\u620f","tid":5649760137,"is_login":1,"content":"\u661f\u9645","access_state":null,"vcode":{"need_vcode":0,"str_reason":"","captcha_vcode_str":"","captcha_code_type":0,"userstatevcode":0},"is_post_visible":0}}時腿堤,證明已經(jīng)發(fā)表成功阀坏。

4-5-3.png

除了模擬發(fā)送請求外,還有另外一種方式:使用無頭瀏覽器訪問
在Python中可以使用Selenium庫來調(diào)用瀏覽器笆檀,寫在代碼里的操作(打開網(wǎng)頁忌堂、點(diǎn)擊……)會變成瀏覽器忠實(shí)地執(zhí)行。這個被控制的瀏覽器可以是Firefox士修,Chrome等,但最常用的還是PhantomJS這個無頭(沒有界面)瀏覽器酒唉。也就是說,通過模擬瀏覽器的點(diǎn)擊沸移、提交等操作來實(shí)現(xiàn)模擬人工處理。

安裝selenium庫:pip install selenium网沾,PhantomJS瀏覽器下載

下面我們以百度貼吧的簽到來體驗(yàn)一下。首先需要找到“簽到”的標(biāo)簽:

4-5-4.png

然后右鍵蕊爵,Copy, Copy XPath辉哥,獲取到控件的XPath路徑证薇。

from selenium import webdriver

url_sign = 'https://tieba.baidu.com/f?kw=%E6%8E%A8%E7%90%86&fr=home'

cookie_str = '輸入你的登錄cookie'
cookies = {}
for line in cookie_str.split(';'):
    key, value = line.split('=', 1)
    cookies[key] = value

headers = {
    'User-agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36'}


def sign():
    browser = webdriver.PhantomJS('D:/phantomjs-2.1.1-windows/bin/phantomjs.exe')
    for key in cookies:
        c = {}
        c['name'] = key
        c['value'] = cookies[key]
        c['domain'] = '.baidu.com'
        c['path'] = '/'
        c['httponly'] = False
        c['secure'] = False
        browser.add_cookie(c)

    browser.get(url_sign)
    browser.implicitly_wait(3)

    sign_btn = browser.find_elements_by_xpath('//*[@id="signstar_wrapper"]/a')[0]
    sign_btn.click()
    sign_btn.click()

    print(browser.page_source.encode('utf-8').decode())

    browser.quit()

PhantomJS的cookie需要單獨(dú)進(jìn)行配置浑度,填充一些必要的參數(shù)鸦概。接著就獲取了“簽到”的標(biāo)簽,并開始點(diǎn)擊(不知道為什么先慷,代碼簽到必須點(diǎn)擊兩下)咨察,然后退出瀏覽器摄狱。
執(zhí)行完成后,刷新查看是否已經(jīng)簽到

4-5-5.png

5. 爬蟲應(yīng)用暢想

經(jīng)過以上的介紹祝谚,可能已經(jīng)對爬蟲有所了解了交惯。對于爬蟲來說,其核心是數(shù)據(jù)源的獲取意荤,如果沒有數(shù)據(jù)源就沒有爬蟲存在的意義只锻。未來的生活可能離不開各種各樣的數(shù)據(jù)炬藤,我們每天從不同的渠道獲取數(shù)據(jù)碴里,也會在各種場景中生產(chǎn)數(shù)據(jù),對于數(shù)據(jù)的收集和分析可以幫助我們更好地行業(yè)規(guī)劃和了解行業(yè)規(guī)律羹膳。所以陵像,數(shù)據(jù)源的選取會給爬蟲的應(yīng)用插上想象的翅膀寇壳。下面我們就腦洞大開,想象一下應(yīng)用爬蟲技術(shù)會給我們提供什么樣的幫助泞歉。

  • 汽車之家 + 爬蟲:通過爬取各個車型的用戶討論內(nèi)容腰耙,加上人工智能分析铲球,對各個車型的購買人群做特性分析⊙∏龋總結(jié)出的歸納數(shù)據(jù)可供4s銷售可以根據(jù)購買者的特征更加精準(zhǔn)地推銷適合的車型然走。
  • 美團(tuán)/大眾點(diǎn)評 + 爬蟲:爬取每個城市美食口味的占比丰刊,最近開店較多的美食類型,結(jié)合GIS技術(shù)分析某一地段消費(fèi)類型占比等寻歧。這些數(shù)據(jù)可以幫助飲食從業(yè)者更好地規(guī)劃開店類型和選址
  • 驢媽媽/馬蜂窩 + 爬蟲:對每個城市的景點(diǎn)關(guān)鍵字進(jìn)行爬取码泛,可以包括最佳出行/觀賞時間、路線選擇晌缘、住宿和飲食安排等磷箕≌竽眩可以對旅游業(yè)從業(yè)者或者游客更好地服務(wù)呜叫。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末朱庆,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子饭于,更是在濱河造成了極大的恐慌掰吕,老刑警劉巖颅痊,帶你破解...
    沈念sama閱讀 217,509評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件斑响,死亡現(xiàn)場離奇詭異舰罚,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)赏陵,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評論 3 394
  • 文/潘曉璐 我一進(jìn)店門蝙搔,熙熙樓的掌柜王于貴愁眉苦臉地迎上來吃型,“玉大人,你說我怎么就攤上這事枉层∧窭” “怎么了血淌?”我有些...
    開封第一講書人閱讀 163,875評論 0 354
  • 文/不壞的土叔 我叫張陵悠夯,是天一觀的道長沦补。 經(jīng)常有香客問我咪橙,道長美侦,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,441評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮具壮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘攘已。我一直安慰自己怜跑,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,488評論 6 392
  • 文/花漫 我一把揭開白布看幼。 她就那樣靜靜地躺著诵姜,像睡著了一般搏熄。 火紅的嫁衣襯著肌膚如雪心例。 梳的紋絲不亂的頭發(fā)上止后,一...
    開封第一講書人閱讀 51,365評論 1 302
  • 那天译株,我揣著相機(jī)與錄音,去河邊找鬼乘寒。 笑死伞辛,一個胖子當(dāng)著我的面吹牛夯缺,可吹牛的內(nèi)容都是我干的踊兜。 我是一名探鬼主播,決...
    沈念sama閱讀 40,190評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼典蝌!你這毒婦竟也來了骏掀?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,062評論 0 276
  • 序言:老撾萬榮一對情侶失蹤际度,失蹤者是張志新(化名)和其女友劉穎涵妥,沒想到半個月后蓬网,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體帆锋,經(jīng)...
    沈念sama閱讀 45,500評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡锯厢,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,706評論 3 335
  • 正文 我和宋清朗相戀三年实辑,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片讯沈。...
    茶點(diǎn)故事閱讀 39,834評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖萍摊,靈堂內(nèi)的尸體忽然破棺而出冰木,到底是詐尸還是另有隱情笼恰,我是刑警寧澤社证,帶...
    沈念sama閱讀 35,559評論 5 345
  • 正文 年R本政府宣布追葡,位于F島的核電站奕短,受9級特大地震影響翎碑,放射性物質(zhì)發(fā)生泄漏日杈。R本人自食惡果不足惜佑刷,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,167評論 3 328
  • 文/蒙蒙 一项乒、第九天 我趴在偏房一處隱蔽的房頂上張望檀何。 院中可真熱鬧,春花似錦栓辜、人聲如沸藕甩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽讯榕。三九已至愚屁,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間送浊,已是汗流浹背罕袋。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評論 1 269
  • 我被黑心中介騙來泰國打工浴讯, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人仰猖。 一個月前我還...
    沈念sama閱讀 47,958評論 2 370
  • 正文 我出身青樓饥侵,卻偏偏與公主長得像躏升,于是被迫代替她去往敵國和親狼忱。 傳聞我的和親對象是個殘疾皇子钻弄,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,779評論 2 354

推薦閱讀更多精彩內(nèi)容