二、伯乐在线爬取所有文章
1. 初始化文件目录
基础环境
- python 3.6.5
- JetBrains PyCharm 2018.1
- 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的命令:
在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节点关系
- 父节点
*上一层节点*
- 子节点
- 兄弟节点
*同胞节点*
- 先辈节点
*父节点,爷爷节点*
- 后代节点
*儿子,孙子*
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获取到图片的下载地址

继承并重写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()
|
数据库表设计

- 三个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()
|

- 所有值变成了list
- 对于这些值做一些处理函数
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
- Python分布式爬虫打造搜索引擎完整版-基于Scrapy、Redis、elasticsearch和django打造一个完整的搜索引擎网站
Python分布式爬虫打造搜索引擎 基于Scrapy.Redis.elasticsearch和django打造一个完整的搜索引擎网站 https://github.com/mtianyan/Artic ...
- 第三百七十节,Python分布式爬虫打造搜索引擎Scrapy精讲—elasticsearch(搜索引擎)用Django实现搜索结果分页
第三百七十节,Python分布式爬虫打造搜索引擎Scrapy精讲—elasticsearch(搜索引擎)用Django实现搜索结果分页 逻辑处理函数 计算搜索耗时 在开始搜索前:start_time ...
- 第三百六十九节,Python分布式爬虫打造搜索引擎Scrapy精讲—elasticsearch(搜索引擎)用Django实现搜索功能
第三百六十九节,Python分布式爬虫打造搜索引擎Scrapy精讲—elasticsearch(搜索引擎)用Django实现搜索功能 Django实现搜索功能 1.在Django配置搜索结果页的路由映 ...
- python分布式爬虫打造搜索引擎--------scrapy实现
最近在网上学习一门关于scrapy爬虫的课程,觉得还不错,以下是目录还在更新中,我觉得有必要好好的做下笔记,研究研究. 第1章 课程介绍 1-1 python分布式爬虫打造搜索引擎简介 07:23 第 ...
- 第三百七十一节,Python分布式爬虫打造搜索引擎Scrapy精讲—elasticsearch(搜索引擎)用Django实现我的搜索以及热门搜索
第三百七十一节,Python分布式爬虫打造搜索引擎Scrapy精讲—elasticsearch(搜索引擎)用Django实现我的搜索以及热门 我的搜素简单实现原理我们可以用js来实现,首先用js获取到 ...
- 第三百五十七节,Python分布式爬虫打造搜索引擎Scrapy精讲—利用开源的scrapy-redis编写分布式爬虫代码
第三百五十七节,Python分布式爬虫打造搜索引擎Scrapy精讲—利用开源的scrapy-redis编写分布式爬虫代码 scrapy-redis是一个可以scrapy结合redis搭建分布式爬虫的开 ...
- 第三百四十七节,Python分布式爬虫打造搜索引擎Scrapy精讲—通过downloadmiddleware中间件全局随机更换user-agent浏览器用户代理
第三百四十七节,Python分布式爬虫打造搜索引擎Scrapy精讲—通过downloadmiddleware随机更换user-agent浏览器用户代理 downloadmiddleware介绍中间件是 ...
- 五十 Python分布式爬虫打造搜索引擎Scrapy精讲—elasticsearch(搜索引擎)用Django实现我的搜索以及热门搜索
第三百七十一节,Python分布式爬虫打造搜索引擎Scrapy精讲—elasticsearch(搜索引擎)用Django实现我的搜索以及热门 我的搜素简单实现原理我们可以用js来实现,首先用js获取到 ...
- 第三百七十二节,Python分布式爬虫打造搜索引擎Scrapy精讲—scrapyd部署scrapy项目
第三百七十二节,Python分布式爬虫打造搜索引擎Scrapy精讲—scrapyd部署scrapy项目 scrapyd模块是专门用于部署scrapy项目的,可以部署和管理scrapy项目 下载地址:h ...
随机推荐
- CONCAT substr group_concat find_in_set
(SELECT p.*,(SELECT CONCAT(m.name,m.id) FROM service_fastfix_category m WHERE m.id=SUBSTR(p.id,1,4)) ...
- vs2017通过snippet代码片断进行标准化注释
我们在进行团队开发时,类的注释已经可以做到自定义了,详细看上篇文章<vs2017通过模块文件添加自定义注释>,而对于方法的注释,我们也需要完善一下,这里我们用到了“代码片断”插件,VS里有 ...
- python基本数据类型,int,bool,str
一丶python基本数据类型 1.int 整数,主要用来进行数学运算. 2.str 字符串,可以保存少量数据并进行相应的操作 3.bool 判断真假.True.False 4.list 存储大量数据, ...
- a标签嵌套a标签效果的两种解决方案
<!-- a标签进行嵌套的时候 --> <a href="#outer">outerA <a href="#inner">i ...
- Sometimes it takes going through something so awful to realize the beauty that is out there in this world.
Sometimes it takes going through something so awful to realize the beauty that is out there in this ...
- eCharts图表(polar极坐标图)
极坐标图 HTML: <div id="eChart"></div> css: #eChart{ width:500px; height:500px; } ...
- java 千分位的添加和去除
转至:http://blog.sina.com.cn/s/blog_8f99a1640102v1xh.html 将一个数字转换为有千分位的格式: NumberFormat numberFormat1 ...
- CocoStudio UIButton setPressedActionEnabled(true) 子控件不跟着缩放
具体情况是这样的:美术给了我 一个按钮的背景图片 一个按钮的文字图片,用背景图片创建一个button,然后把文字图片添加进去(注意关闭文字图片的交互功能) 设置UIButton setPressed ...
- OutOfMemoryError异常 和 StackOverflowError异常
OutOfMemoryError异常 StackOverflowError异常 程序计数器 无 无 Java虚拟机栈 如果虚拟机栈可扩展,扩展时无法申请到足够内存 线程请求的栈深度大于虚拟机所 ...
- 触发OOM杀掉了mysql
中午收到反馈平台所有账号全部无法登录,运维就是苦逼,饭都没吃就跑来处理紧急故障,先自己测试了下确实无法登录进系统,登录服务器检查,发现mysql数据库挂掉了,定位到了原因就赶紧重启mysql吧,结果启 ...