二、伯樂在線爬取所有文章
1. 初始化文件目錄
基礎環境
- python 3.6.5
- JetBrains PyCharm 2018.1
- mysql+navicat
為了便於日后的部署:我們開發使用了虛擬環境。
1 |
pip install virtualenv |
scrapy項目初始化介紹
自行官網下載py35對應得whl文件進行pip離線安裝
Scrapy 1.3.3安裝時報錯:
Failed building wheel for Twisted
點擊下方鏈接,即可找到並下載相對應的whl文件:
https://www.lfd.uci.edu/~gohlke/pythonlibs/#lxml
例如,出現“ Failed building wheel for Twisted”則下載相應python版本的Twisted文件。
筆者用的是Python3.6版本,則找到Twisted-17.1.0-cp36-cp36m-win_amd64.whl文件進行下載即可。切忌修改文件名!!
命令行創建scrapy項目
1 |
cd desktop |
scrapy目錄結構
scrapy借鑒了django的項目思想
scrapy.cfg
:配置文件。setings.py
:設置
1 |
SPIDER_MODULES = ['ArticleSpider.spiders'] #存放spider的路徑 |
pipelines.py:
做跟數據存儲相關的東西
middilewares.py:
自己定義的middlewares 定義方法,處理響應的IO操作
init.py:
項目的初始化文件。
items.py:
定義我們所要爬取的信息的相關屬性。Item對象是種類似於表單,用來保存獲取到的數據
創建我們的spider
1 |
cd ArticleSpider |
可以看到直接為我們創建好的空項目里已經有了模板代碼。如下:
1 |
# -*- coding: utf-8 -*- |
scray在命令行啟動某一個Spyder的命令:
1 |
scrapy crawl jobbole |
在windows報出錯誤
ImportError: No module named 'win32api'
1 |
pip install pypiwin32#解決 |
創建我們的調試工具類*
在項目根目錄里創建main.py
作為調試工具文件
main.py
# -*- coding: utf-8 -*-
# @Time : 2018/5/29 16:16
# @Author : xinjie
from scrapy.cmdline import execute
import sys
import os
#將系統當前目錄設置為項目根目錄
#os.path.abspath(__file__)為當前文件所在絕對路徑
#os.path.dirname為文件所在目錄
#H:\CodePath\spider\ArticleSpider\main.py
#H:\CodePath\spider\ArticleSpider
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
#執行命令,相當於在控制台cmd輸入改名了
execute(["scrapy", "crawl" , "jobbole"])
settings.py的設置不遵守reboots協議
ROBOTSTXT_OBEY = False
在jobble.py打上斷點:
1 |
def parse(self, response): |
可以看到他返回的htmlresponse對象:
對象內部:
- body:網頁內容
- _DEFAULT_ENCODING= ‘ascii’
- encoding= ‘utf-8’
可以看出scrapy已經為我們做到了將網頁下載下來。而且編碼也進行了轉換.
2. 提取伯樂在線內容
xpath的使用
xpath讓你可以不懂前端html,不看html的詳細結構,只需要會右鍵查看就能獲取網頁上任何內容。速度遠超beautifulsoup。
目錄:
1. xpath簡介
2. xpath術語與語法
3. xpath抓取誤區:javasrcipt生成html與html源文件的區別
4. xpath抓取實例
為什么要使用xpath?
- xpath使用路徑表達式在xml和html中進行導航
- xpath包含有一個標准函數庫
- xpath是一個w3c的標准
- xpath速度要遠遠超beautifulsoup。
xpath節點關系
- 父節點
*上一層節點*
- 子節點
- 兄弟節點
*同胞節點*
- 先輩節點
*父節點,爺爺節點*
- 后代節點
*兒子,孫子*
xpath語法:
表達式 | 說明 |
---|---|
article | 選取所有article元素的所有子節點 |
/article | 選取根元素article |
article/a | 選取所有屬於article的子元素的a元素 |
//div | 選取所有div元素(不管出現在文檔里的任何地方) |
article//div | 選取所有屬於article元素的后代的div元素,不管它出現在article之下的任何位置 |
//@class | 選取所有名為class的屬性 |
xpath語法-謂語:
表達式 | 說明 |
---|---|
/article/div[1 | 選取屬於article子元素的第一個div元素 |
/article/div[last()] | 選取屬於article子元素的最后一個div元素 |
/article/div[last()-1] | 選取屬於article子元素的倒數第二個div元素 |
//div[@color] | 選取所有擁有color屬性的div元素 |
//div[@color=’red’] | 選取所有color屬性值為red的div元素 |
xpath語法:
表達式 | 說明 |
---|---|
/div/* | 選取屬於div元素的所有子節點 |
//* | 選取所有元素 |
//div[@*] | 選取所有帶屬性的div 元素 |
//div/a 丨//div/p | 選取所有div元素的a和p元素 |
//span丨//ul | 選取文檔中的span和ul元素 |
article/div/p丨//span | 選取所有屬於article元素的div元素的p元素以及文檔中所有的 span元素 |
xpath抓取誤區
取某一個網頁上元素的xpath地址
在標題處右鍵使用firebugs查看元素。
然后在<h1>2016 騰訊軟件開發面試題(部分)</h1>
右鍵查看xpath
1 |
# -*- coding: utf-8 -*- |
調試debug可以看到
1 |
re_selector =(selectorlist)[] |
可以看到返回的是一個空列表,
列表是為了如果我們當前的xpath路徑下還有層級目錄時可以進行選取
空說明沒取到值:
我們可以來chorme里觀察一下
chorme取到的值
//*[@id="post-110287"]/div[1]/h1
chormexpath代碼
1 |
# -*- coding: utf-8 -*- |
可以看出此時可以取到值
分析頁面,可以發現頁面內有一部html是通過JavaScript ajax交互來生成的,因此在f12檢查元素時的頁面結構里有,而xpath不對
xpath是基於html源代碼文件結構來找的
xpath可以有多種多樣的寫法:
1 |
re_selector = response.xpath("/html/body/div[1]/div[3]/div[1]/div[1]/h1/text()") |
推薦使用id型。因為頁面id唯一。
推薦使用class型,因為后期循環爬取可擴展通用性強。
通過了解了這些此時我們已經可以抓取到頁面的標題,此時可以使用xpath利器照貓畫虎抓取任何內容。只需要點擊右鍵查看xpath。
開啟控制台調試
scrapy shell http://blog.jobbole.com/110287/
完整的xpath提取伯樂在線字段代碼
1 |
# -*- coding: utf-8 -*- |
css選擇器的使用:
1 |
# 通過css選擇器提取字段 |
3. 爬取所有文章
yield關鍵字
#使用request下載詳情頁面,下載完成后回調方法parse_detail()提取文章內容中的字段 |
scrapy.http import Request下載網頁
1 |
from scrapy.http import Request |
parse拼接網址應對herf內有可能網址不全
1 |
from urllib import parse |
class層級關系
1 |
next_url = response.css(".next.page-numbers::attr(href)").extract_first("") |
twist異步機制
Scrapy使用了Twisted作為框架,Twisted有些特殊的地方是它是事件驅動的,並且比較適合異步的代碼。在任何情況下,都不要寫阻塞的代碼。阻塞的代碼包括:
- 訪問文件、數據庫或者Web
- 產生新的進程並需要處理新進程的輸出,如運行shell命令
- 執行系統層次操作的代碼,如等待系統隊列
實現全部文章字段下載的代碼:
1 |
def parse(self, response): |
全部文章的邏輯流程圖
4. scrapy的items整合字段
數據爬取的任務就是從非結構的數據中提取出結構性的數據。
items 可以讓我們自定義自己的字段(類似於字典,但比字典的功能更齊全)
在當前頁,需要提取多個url
原始寫法,extract之后則生成list列表,無法進行二次篩選:
1 |
post_urls = response.css("#archive .floated-thumb .post-thumb a::attr(href)").extract() |
改進寫法:
1 |
post_nodes = response.css("#archive .floated-thumb .post-thumb a") |
在下載網頁的時候把獲取到的封面圖的url傳給parse_detail的response
在下載網頁時將這個封面url獲取到,並通過meta將他發送出去。在callback的回調函數中接收該值
1 |
yield Request(url=parse.urljoin(response.url,post_url),meta={"front_image_url":image_url},callback=self.parse_detail) |
urljoin的好處
如果你沒有域名,我就從response里取出來,如果你有域名則我對你起不了作用了
編寫我們自定義的item並在jobboled.py中填充。
1 |
class JobBoleArticleItem(scrapy.Item): |
import之后實例化,實例化之后填充:
1 |
1. from ArticleSpider.items import JobBoleArticleItem |
yield article_item將這個item傳送到pipelines中
pipelines可以接收到傳送過來的item
將setting.py中的pipeline配置取消注釋
1 |
# Configure item pipelines |
當我們的item被傳輸到pipeline我們可以將其進行存儲到數據庫等工作
setting設置下載圖片pipeline
1 |
ITEM_PIPELINES={ |
H:\CodePath\pyEnvs\articlespider3\Lib\site-packages\scrapy\pipelines
里面有三個scrapy默認提供的pipeline
提供了文件,圖片,媒體。
ITEM_PIPELINES是一個數據管道的登記表,每一項具體的數字代表它的優先級,數字越小,越早進入。
setting設置下載圖片的地址
1 |
# IMAGES_MIN_HEIGHT = 100 |
設置下載圖片的最小高度,寬度。
新建文件夾images在
1 |
IMAGES_URLS_FIELD = "front_image_url" |
安裝PILpip install pillow
定制自己的pipeline使其下載圖片后能保存下它的本地路徑
get_media_requests()接收一個迭代器對象下載圖片
item_completed獲取到圖片的下載地址
繼承並重寫item_completed()
1 |
from scrapy.pipelines.images import ImagesPipeline |
setting中設置使用我們自定義的pipeline,而不是系統自帶的
1 |
ITEM_PIPELINES = { |
圖片url的md5處理
新建package utils
1 |
import hashlib |
不確定用戶傳入的是不是:
1 |
def get_md5(url): |
在jobbole.py中將url的md5保存下來
1 |
from ArticleSpider.utils.common import get_md5 |
5. 數據保存到本地文件以及mysql中
保存到本地json文件
import codecs打開文件避免一些編碼問題,自定義JsonWithEncodingPipeline實現json本地保存
1 |
class JsonWithEncodingPipeline(object): |
setting.py注冊pipeline
1 |
ITEM_PIPELINES = { |
scrapy exporters JsonItemExporter導出
scrapy自帶的導出:
- 'CsvItemExporter',
- 'XmlItemExporter',
- 'JsonItemExporter'
from scrapy.exporters import JsonItemExporter
1 |
class JsonExporterPipleline(object): |
設置setting.py注冊該pipeline
1 |
'ArticleSpider.pipelines.JsonExporterPipleline ': 2 |
保存到數據庫(mysql)
數據庫設計數據表,表的內容字段是和item一致的。數據庫與item的關系。類似於django中model與form的關系。
日期的轉換,將字符串轉換為datetime
1 |
import datetime |
數據庫表設計
- 三個num字段均設置不能為空,然后默認0.
- content設置為longtext
- 主鍵設置為url_object_id
數據庫驅動安裝pip install mysqlclient
Linux報錯解決方案:
ubuntu:sudo apt-get install libmysqlclient-dev
centos:sudo yum install python-devel mysql-devel
保存到數據庫pipeline(同步)編寫
1 |
import MySQLdb |
保存到數據庫的(異步Twisted)編寫
因為我們的爬取速度可能大於數據庫存儲的速度。異步操作。
設置可配置參數
seeting.py設置
1 |
MYSQL_HOST = "127.0.0.1" |
代碼中獲取到設置的可配置參數
twisted異步:
1 |
import MySQLdb.cursors |
- 所有值變成了list
- 對於這些值做一些處理函數
item.py中對於item process處理函數
MapCompose可以傳入函數對於該字段進行處理,而且可以傳入多個
1 |
from scrapy.loader.processors import MapCompose |
注意:此處的自定義方法一定要寫在代碼前面。
1 |
create_date = scrapy.Field( |
只取list中的第一個值。
自定義itemloader實現默認提取第一個
1 |
class ArticleItemLoader(ItemLoader): |
list保存原值
1 |
def return_value(value): |
下載圖片pipeline增加if增強通用性
1 |
class ArticleImagePipeline(ImagesPipeline): |
自定義的item帶處理函數的完整代碼
1 |
class JobBoleArticleItem(scrapy.Item): |
本文結束感謝您的閱讀
文章學習來自於@天涯明月笙博客,原文鏈接:http://blog.mtianyan.cn/post/1cc4531e.html