Scrapy分布式爬蟲打造搜索引擎- (二)伯樂在線爬取所有文章


二、伯樂在線爬取所有文章

1. 初始化文件目錄

基礎環境

  1. python 3.6.5
  2. JetBrains PyCharm 2018.1
  3. mysql+navicat

為了便於日后的部署:我們開發使用了虛擬環境。

1
2
3
4
5
6
7
8
9
10
11
pip install virtualenv
pip install virtualenvwrapper-win
安裝虛擬環境管理
mkvirtualenv articlespider3
創建虛擬環境
workon articlespider3
直接進入虛擬環境
deactivate
退出激活狀態
workon
知道有哪些虛擬環境

 

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
2
3
cd desktop

scrapy startproject ArticleSpider

scrapy目錄結構

scrapy借鑒了django的項目思想

  • scrapy.cfg:配置文件。
  • setings.py:設置
1
2
SPIDER_MODULES = ['ArticleSpider.spiders'] #存放spider的路徑
NEWSPIDER_MODULE = 'ArticleSpider.spiders'

pipelines.py:

做跟數據存儲相關的東西

middilewares.py:

自己定義的middlewares 定義方法,處理響應的IO操作

init.py:

項目的初始化文件。

items.py:

定義我們所要爬取的信息的相關屬性。Item對象是種類似於表單,用來保存獲取到的數據

 

創建我們的spider

1
2
cd ArticleSpider
scrapy genspider jobbole blog.jobbole.com

可以看到直接為我們創建好的空項目里已經有了模板代碼。如下:

1
2
3
4
5
6
7
8
9
10
11
12
# -*- coding: utf-8 -*-
import scrapy


class JobboleSpider(scrapy.Spider):
name = "jobbole"
allowed_domains = ["blog.jobbole.com"]
# start_urls是一個帶爬的列表,
#spider會為我們把請求下載網頁做到,直接到parse階段
start_urls = ['http://blog.jobbole.com/']
def parse(self, response):
pass

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
2
def parse(self, response):
pass

 

可以看到他返回的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節點關系

  1. 父節點*上一層節點*
  2. 子節點
  3. 兄弟節點*同胞節點*
  4. 先輩節點*父節點,爺爺節點*
  5. 后代節點*兒子,孫子*
    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抓取誤區

firebugs插件

取某一個網頁上元素的xpath地址

如:http://blog.jobbole.com/110287/

在標題處右鍵使用firebugs查看元素。
然后在<h1>2016 騰訊軟件開發面試題(部分)</h1>右鍵查看xpath

1
2
3
4
5
6
7
8
9
10
11
12
# -*- coding: utf-8 -*-
import scrapy

class JobboleSpider(scrapy.Spider):
name = "jobbole"
allowed_domains = ["blog.jobbole.com"]
start_urls = ['http://blog.jobbole.com/110287/']

def parse(self, response):
re_selector = response.xpath("/html/body/div[3]/div[3]/div[1]/div[1]/h1")
# print(re_selector)
pass

調試debug可以看到

1
re_selector =(selectorlist)[]

 

可以看到返回的是一個空列表,
列表是為了如果我們當前的xpath路徑下還有層級目錄時可以進行選取
空說明沒取到值:

 

我們可以來chorme里觀察一下

chorme取到的值
//*[@id="post-110287"]/div[1]/h1

chormexpath代碼

1
2
3
4
5
6
7
8
9
10
11
12
13
# -*- coding: utf-8 -*-
import scrapy


class JobboleSpider(scrapy.Spider):
name = "jobbole"
allowed_domains = ["blog.jobbole.com"]
start_urls = ['http://blog.jobbole.com/110287/']

def parse(self, response):
re_selector = response.xpath('//*[@id="post-110287"]/div[1]/h1')
# print(re_selector)
pass

可以看出此時可以取到值

分析頁面,可以發現頁面內有一部html是通過JavaScript ajax交互來生成的,因此在f12檢查元素時的頁面結構里有,而xpath不對
xpath是基於html源代碼文件結構來找的

xpath可以有多種多樣的寫法:

1
2
3
re_selector = response.xpath("/html/body/div[1]/div[3]/div[1]/div[1]/h1/text()")
re2_selector = response.xpath('//*[@id="post-110287"]/div[1]/h1/text()')
re3_selector = response.xpath('//div[@class="entry-header“]/h1/text()')

推薦使用id型。因為頁面id唯一。

推薦使用class型,因為后期循環爬取可擴展通用性強。

通過了解了這些此時我們已經可以抓取到頁面的標題,此時可以使用xpath利器照貓畫虎抓取任何內容。只需要點擊右鍵查看xpath。

開啟控制台調試

scrapy shell http://blog.jobbole.com/110287/

完整的xpath提取伯樂在線字段代碼

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# -*- coding: utf-8 -*-
import scrapy
import re

class JobboleSpider(scrapy.Spider):
name = "jobbole"
allowed_domains = ["blog.jobbole.com"]
start_urls = ['http://blog.jobbole.com/110287/']

def parse(self, response):
#提取文章的具體字段
title = response.xpath('//div[@class="entry-header"]/h1/text()').extract_first("")
create_date = response.xpath("//p[@class='entry-meta-hide-on-mobile']/text()").extract()[0].strip().replace("·","").strip()
praise_nums = response.xpath("//span[contains(@class, 'vote-post-up')]/h10/text()").extract()[0]
fav_nums = response.xpath("//span[contains(@class, 'bookmark-btn')]/text()").extract()[0]
match_re = re.match(".*?(\d+).*", fav_nums)
if match_re:
fav_nums = match_re.group(1)

comment_nums = response.xpath("//a[@href='#article-comment']/span/text()").extract()[0]
match_re = re.match(".*?(\d+).*", comment_nums)
if match_re:
comment_nums = match_re.group(1)

content = response.xpath("//div[@class='entry']").extract()[0]

tag_list = response.xpath("//p[@class='entry-meta-hide-on-mobile']/a/text()").extract()
tag_list = [element for element in tag_list if not element.strip().endswith("評論")]
tags = ",".join(tag_list)
pass

css選擇器的使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 通過css選擇器提取字段
# front_image_url = response.meta.get("front_image_url", "") #文章封面圖
title = response.css(".entry-header h1::text").extract_first()
create_date = response.css("p.entry-meta-hide-on-mobile::text").extract()[0].strip().replace("·","").strip()
praise_nums = response.css(".vote-post-up h10::text").extract()[0]
fav_nums = response.css(".bookmark-btn::text").extract()[0]
match_re = re.match(".*?(\d+).*", fav_nums)
if match_re:
fav_nums = int(match_re.group(1))
else:
fav_nums = 0

comment_nums = response.css("a[href='#article-comment'] span::text").extract()[0]
match_re = re.match(".*?(\d+).*", comment_nums)
if match_re:
comment_nums = int(match_re.group(1))
else:
comment_nums = 0

content = response.css("div.entry").extract()[0]

tag_list = response.css("p.entry-meta-hide-on-mobile a::text").extract()
tag_list = [element for element in tag_list if not element.strip().endswith("評論")]
tags = ",".join(tag_list)
pass

 

3. 爬取所有文章

yield關鍵字

#使用request下載詳情頁面,下載完成后回調方法parse_detail()提取文章內容中的字段
yield Request(url=parse.urljoin(response.url,post_url),callback=self.parse_detail)

scrapy.http import Request下載網頁

1
2
from scrapy.http import Request
Request(url=parse.urljoin(response.url,post_url),callback=self.parse_detail)

parse拼接網址應對herf內有可能網址不全

1
2
3
4
from urllib import parse
url=parse.urljoin(response.url,post_url)
parse.urljoin("http://blog.jobbole.com/all-posts/","http://blog.jobbole.com/111535/")
#結果為http://blog.jobbole.com/111535/

class層級關系

1
2
next_url = response.css(".next.page-numbers::attr(href)").extract_first("")
#如果.next .pagenumber 是指兩個class為層級關系。而不加空格為同一個標簽

twist異步機制

Scrapy使用了Twisted作為框架,Twisted有些特殊的地方是它是事件驅動的,並且比較適合異步的代碼。在任何情況下,都不要寫阻塞的代碼。阻塞的代碼包括:

  • 訪問文件、數據庫或者Web
  • 產生新的進程並需要處理新進程的輸出,如運行shell命令
  • 執行系統層次操作的代碼,如等待系統隊列

實現全部文章字段下載的代碼:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def parse(self, response):
"""
1. 獲取文章列表頁中的文章url並交給scrapy下載后並進行解析
2. 獲取下一頁的url並交給scrapy進行下載, 下載完成后交給parse
"""
# 解析列表頁中的所有文章url並交給scrapy下載后並進行解析
post_urls = response.css("#archive .floated-thumb .post-thumb a::attr(href)").extract()
for post_url in post_urls:
#request下載完成之后,回調parse_detail進行文章詳情頁的解析
# Request(url=post_url,callback=self.parse_detail)
print(response.url)
print(post_url)
yield Request(url=parse.urljoin(response.url,post_url),callback=self.parse_detail)
#遇到href沒有域名的解決方案
#response.url + post_url
print(post_url)
# 提取下一頁並交給scrapy進行下載
next_url = response.css(".next.page-numbers::attr(href)").extract_first("")
if next_url:
yield Request(url=parse.urljoin(response.url, post_url), callback=self.parse)

 

全部文章的邏輯流程圖

所有文章流程圖

4. scrapy的items整合字段

數據爬取的任務就是從非結構的數據中提取出結構性的數據。
items 可以讓我們自定義自己的字段(類似於字典,但比字典的功能更齊全)

在當前頁,需要提取多個url

原始寫法,extract之后則生成list列表,無法進行二次篩選:

1
post_urls = response.css("#archive .floated-thumb .post-thumb a::attr(href)").extract()

改進寫法:

1
2
3
4
5
post_nodes = response.css("#archive .floated-thumb .post-thumb a")
for post_node in post_nodes:
#獲取封面圖的url
image_url = post_node.css("img::attr(src)").extract_first("")
post_url = post_node.css("::attr(href)").extract_first("")

在下載網頁的時候把獲取到的封面圖的url傳給parse_detail的response
在下載網頁時將這個封面url獲取到,並通過meta將他發送出去。在callback的回調函數中接收該值

1
2
3
yield Request(url=parse.urljoin(response.url,post_url),meta={"front_image_url":image_url},callback=self.parse_detail)

front_image_url = response.meta.get("front_image_url", "")

urljoin的好處
如果你沒有域名,我就從response里取出來,如果你有域名則我對你起不了作用了

編寫我們自定義的item並在jobboled.py中填充。

1
2
3
4
5
6
7
8
9
10
11
12
class JobBoleArticleItem(scrapy.Item):
title = scrapy.Field()
create_date = scrapy.Field()
url = scrapy.Field()
url_object_id = scrapy.Field()
front_image_url = scrapy.Field()
front_image_path = scrapy.Field()
praise_nums = scrapy.Field()
comment_nums = scrapy.Field()
fav_nums = scrapy.Field()
content = scrapy.Field()
tags = scrapy.Field()

import之后實例化,實例化之后填充:

1
2
3
4
5
6
7
8
9
10
11
1. from ArticleSpider.items import JobBoleArticleItem
2. article_item = JobBoleArticleItem()
3. article_item["title"] = title
article_item["url"] = response.url
article_item["create_date"] = create_date
article_item["front_image_url"] = [front_image_url]
article_item["praise_nums"] = praise_nums
article_item["comment_nums"] = comment_nums
article_item["fav_nums"] = fav_nums
article_item["tags"] = tags
article_item["content"] = content

yield article_item將這個item傳送到pipelines中
pipelines可以接收到傳送過來的item
將setting.py中的pipeline配置取消注釋

1
2
3
4
5
# Configure item pipelines
# See http://scrapy.readthedocs.org/en/latest/topics/item-pipeline.html
ITEM_PIPELINES = {
'ArticleSpider.pipelines.ArticlespiderPipeline': 300,
}

 

當我們的item被傳輸到pipeline我們可以將其進行存儲到數據庫等工作

setting設置下載圖片pipeline

1
2
3
ITEM_PIPELINES={
'scrapy.pipelines.images.ImagesPipeline': 1,
}

 

H:\CodePath\pyEnvs\articlespider3\Lib\site-packages\scrapy\pipelines
里面有三個scrapy默認提供的pipeline
提供了文件,圖片,媒體。

ITEM_PIPELINES是一個數據管道的登記表,每一項具體的數字代表它的優先級,數字越小,越早進入。

setting設置下載圖片的地址

1
2
# IMAGES_MIN_HEIGHT = 100
# IMAGES_MIN_WIDTH = 100

 

設置下載圖片的最小高度,寬度。

新建文件夾images在

1
2
3
IMAGES_URLS_FIELD = "front_image_url"
project_dir = os.path.abspath(os.path.dirname(__file__))
IMAGES_STORE = os.path.join(project_dir, 'images')

安裝PIL
pip install pillow

定制自己的pipeline使其下載圖片后能保存下它的本地路徑
get_media_requests()接收一個迭代器對象下載圖片
item_completed獲取到圖片的下載地址

自定義圖片pipeline的調試信息

繼承並重寫item_completed()

1
2
3
4
5
6
7
8
9
10
from scrapy.pipelines.images import ImagesPipeline

class ArticleImagePipeline(ImagesPipeline):
#重寫該方法可從result中獲取到圖片的實際下載地址
def item_completed(self, results, item, info):
for ok, value in results:
image_file_path = value["path"]
item["front_image_path"] = image_file_path

return item

setting中設置使用我們自定義的pipeline,而不是系統自帶的

1
2
3
4
5
ITEM_PIPELINES = {
'ArticleSpider.pipelines.ArticlespiderPipeline': 300,
# 'scrapy.pipelines.images.ImagesPipeline': 1,
'ArticleSpider.pipelines.ArticleImagePipeline':1,
}

保存下來的本地地址

圖片url的md5處理
新建package utils

1
2
3
4
5
6
7
8
9
import hashlib

def get_md5(url):
m = hashlib.md5()
m.update(url)
return m.hexdigest()

if __name__ == "__main__":
print(get_md5("http://jobbole.com".encode("utf-8")))

不確定用戶傳入的是不是:

1
2
3
4
5
6
7
def get_md5(url):
#str就是unicode了
if isinstance(url, str):
url = url.encode("utf-8")
m = hashlib.md5()
m.update(url)
return m.hexdigest()

在jobbole.py中將url的md5保存下來

1
2
from ArticleSpider.utils.common import get_md5
article_item["url_object_id"] = get_md5(response.url)

5. 數據保存到本地文件以及mysql中

保存到本地json文件

import codecs打開文件避免一些編碼問題,自定義JsonWithEncodingPipeline實現json本地保存

1
2
3
4
5
6
7
8
9
10
11
12
class JsonWithEncodingPipeline(object):
#自定義json文件的導出
def __init__(self):
self.file = codecs.open('article.json', 'w', encoding="utf-8")
def process_item(self, item, spider):
#將item轉換為dict,然后生成json對象,false避免中文出錯
lines = json.dumps(dict(item), ensure_ascii=False) + "\n"
self.file.write(lines)
return item
#當spider關閉的時候
def spider_closed(self, spider):
self.file.close()

setting.py注冊pipeline

1
2
3
4
5
ITEM_PIPELINES = {
'ArticleSpider.pipelines.JsonWithEncodingPipeline': 2,
# 'scrapy.pipelines.images.ImagesPipeline': 1,
'ArticleSpider.pipelines.ArticleImagePipeline':1,
}

scrapy exporters JsonItemExporter導出

scrapy自帶的導出:

- 'CsvItemExporter', 
- 'XmlItemExporter',
- 'JsonItemExporter'

from scrapy.exporters import JsonItemExporter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class JsonExporterPipleline(object):
#調用scrapy提供的json export導出json文件
def __init__(self):
self.file = open('articleexport.json', 'wb')
self.exporter = JsonItemExporter(self.file, encoding="utf-8", ensure_ascii=False)
self.exporter.start_exporting()

def close_spider(self, spider):
self.exporter.finish_exporting()
self.file.close()

def process_item(self, item, spider):
self.exporter.export_item(item)
return item

設置setting.py注冊該pipeline

1
'ArticleSpider.pipelines.JsonExporterPipleline ': 2

保存到數據庫(mysql)

數據庫設計數據表,表的內容字段是和item一致的。數據庫與item的關系。類似於django中model與form的關系。
日期的轉換,將字符串轉換為datetime

1
2
3
4
5
import datetime
try:
create_date = datetime.datetime.strptime(create_date, "%Y/%m/%d").date()
except Exception as e:
create_date = datetime.datetime.now().date()

數據庫表設計

jobbole數據表設計

  • 三個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
2
3
4
5
6
7
8
9
10
11
12
13
14
import MySQLdb
class MysqlPipeline(object):
#采用同步的機制寫入mysql
def __init__(self):
self.conn = MySQLdb.connect('127.0.0.1', 'root', 'mima', 'article_spider', charset="utf8", use_unicode=True)
self.cursor = self.conn.cursor()

def process_item(self, item, spider):
insert_sql = """
insert into jobbole_article(title, url, create_date, fav_nums)
VALUES (%s, %s, %s, %s)
"""
self.cursor.execute(insert_sql, (item["title"], item["url"], item["create_date"], item["fav_nums"]))
self.conn.commit()

保存到數據庫的(異步Twisted)編寫
因為我們的爬取速度可能大於數據庫存儲的速度。異步操作。
設置可配置參數
seeting.py設置

1
2
3
4
MYSQL_HOST = "127.0.0.1"
MYSQL_DBNAME = "article_spider"
MYSQL_USER = "root"
MYSQL_PASSWORD = "123456"

 

代碼中獲取到設置的可配置參數
twisted異步:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
import MySQLdb.cursors
from twisted.enterprise import adbapi

#連接池ConnectionPool
# def __init__(self, dbapiName, *connargs, **connkw):
class MysqlTwistedPipline(object):
def __init__(self, dbpool):
self.dbpool = dbpool

@classmethod
def from_settings(cls, settings):
dbparms = dict(
host = settings["MYSQL_HOST"],
db = settings["MYSQL_DBNAME"],
user = settings["MYSQL_USER"],
passwd = settings["MYSQL_PASSWORD"],
charset='utf8',
cursorclass=MySQLdb.cursors.DictCursor,
use_unicode=True,
)
#**dbparms-->("MySQLdb",host=settings['MYSQL_HOST']
dbpool = adbapi.ConnectionPool("MySQLdb", **dbparms)

return cls(dbpool)

def process_item(self, item, spider):
#使用twisted將mysql插入變成異步執行
query = self.dbpool.runInteraction(self.do_insert, item)
query.addErrback(self.handle_error, item, spider) #處理異常

def handle_error(self, failure, item, spider):
#處理異步插入的異常
print (failure)

def do_insert(self, cursor, item):
#執行具體的插入
#根據不同的item 構建不同的sql語句並插入到mysql中
insert_sql, params = item.get_insert_sql()
cursor.execute(insert_sql, params)

```
可選django.items

https://github.com/scrapy-plugins/scrapy-djangoitem

可以讓我們保存的item直接變成django的models.

#### scrapy的itemloader來維護提取代碼

itemloadr提供了一個容器,讓我們配置某一個字段該使用哪種規則。
add_css add_value add_xpath
```python
from scrapy.loader import ItemLoader
# 通過item loader加載item
front_image_url = response.meta.get("front_image_url", "") # 文章封面圖
item_loader = ItemLoader(item=JobBoleArticleItem(), response=response)
item_loader.add_css("title", ".entry-header h1::text")
item_loader.add_value("url", response.url)
item_loader.add_value("url_object_id", get_md5(response.url))
item_loader.add_css("create_date", "p.entry-meta-hide-on-mobile::text")
item_loader.add_value("front_image_url", [front_image_url])
item_loader.add_css("praise_nums", ".vote-post-up h10::text")
item_loader.add_css("comment_nums", "a[href='#article-comment'] span::text")
item_loader.add_css("fav_nums", ".bookmark-btn::text")
item_loader.add_css("tags", "p.entry-meta-hide-on-mobile a::text")
item_loader.add_css("content", "div.entry")
#調用這個方法來對規則進行解析生成item對象
article_item = item_loader.load_item()

直接使用itemloader的問題

  1. 所有值變成了list
  2. 對於這些值做一些處理函數
    item.py中對於item process處理函數
    MapCompose可以傳入函數對於該字段進行處理,而且可以傳入多個
1
2
3
4
5
6
7
from scrapy.loader.processors import MapCompose
def add_mtianyan(value):
return value+"-mtianyan"

title = scrapy.Field(
input_processor=MapCompose(lambda x:x+"mtianyan",add_mtianyan),
)

注意:此處的自定義方法一定要寫在代碼前面。

1
2
3
4
create_date = scrapy.Field(
input_processor=MapCompose(date_convert),
output_processor=TakeFirst()
)

只取list中的第一個值。

自定義itemloader實現默認提取第一個

1
2
3
class ArticleItemLoader(ItemLoader):
#自定義itemloader實現默認提取第一個
default_output_processor = TakeFirst()

list保存原值

1
2
3
4
5
6
def return_value(value):
return value

front_image_url = scrapy.Field(
output_processor=MapCompose(return_value)
)

下載圖片pipeline增加if增強通用性

1
2
3
4
5
6
7
8
9
class ArticleImagePipeline(ImagesPipeline):
#重寫該方法可從result中獲取到圖片的實際下載地址
def item_completed(self, results, item, info):
if "front_image_url" in item:
for ok, value in results:
image_file_path = value["path"]
item["front_image_path"] = image_file_path

return item

自定義的item帶處理函數的完整代碼

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class JobBoleArticleItem(scrapy.Item):
title = scrapy.Field()
create_date = scrapy.Field(
input_processor=MapCompose(date_convert),
)
url = scrapy.Field()
url_object_id = scrapy.Field()
front_image_url = scrapy.Field(
output_processor=MapCompose(return_value)
)
front_image_path = scrapy.Field()
praise_nums = scrapy.Field(
input_processor=MapCompose(get_nums)
)
comment_nums = scrapy.Field(
input_processor=MapCompose(get_nums)
)
fav_nums = scrapy.Field(
input_processor=MapCompose(get_nums)
)
#因為tag本身是list,所以要重寫
tags = scrapy.Field(
input_processor=MapCompose(remove_comment_tags),
output_processor=Join(",")
)
content = scrapy.Field()

 

本文結束感謝您的閱讀

文章學習來自於@天涯明月笙博客,原文鏈接:http://blog.mtianyan.cn/post/1cc4531e.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM