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)信息仿吞。
之后為了更好查看這段json信息滑频,使用JSON在線解析工具
完整的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é)竭业。
根據(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使用了一種基于XPath
和 CSS
表達(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è)計如下:
在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ā)送請求,就能讓你的程序假扮成剛才登錄的那個瀏覽器唾糯,得到只有登錄后才能看到的頁面怠硼。
然后我們在源代碼中找到進(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變化:
發(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/add
的POST
請求旱易。接著查看一下請求的數(shù)據(jù):
上面這些數(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ā)表成功阀坏。
除了模擬發(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)簽:
然后右鍵蕊爵,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)簽到
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ù)呜叫。