博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Scrapy 学习记录
阅读量:6300 次
发布时间:2019-06-22

本文共 16318 字,大约阅读时间需要 54 分钟。

首先,安装virtualenv虚拟环境

启动虚拟环境,进入希望保存项目的目录

使用下面的命令新建一个scrapy的项目(由于pycharm中没有内置scrapy的项目,只能手动创建)

scrapy startproject ArticleSpider(项目名称)

系统返回表示成功

New Scrapy project 'ArticleSpider', using template directory '/Users/qiuyang/virtualenv/scrapy/lib/python3.6/site-packages/scrapy/templates/project', created in:    /Users/qiuyang/Documents/python/ArticleSpiderYou can start your first spider with:    cd ArticleSpider    scrapy genspider example example.com
系统返回内容

 

在pycharm中打开项目文件,设置解释器为虚拟环境,然后就可以进行代码的编写工作了

在命令行,在项目目录中创建爬虫文件(指定爬虫文件名称,爬取起始地址):

$ scrapy genspider jobbole blog.jobbole.com

这时在spides目录中就新建了一个jobbole.py的文件

 

由于pycharm没有默认的scrapy的debug模式,需要自行创建

在项目的根目录下创建main.py文件

from scrapy.cmdline import executeimport sys,os# 获取项目路径,将项目路径添加到系统path路径中sys.path.append(os.path.dirname(os.path.abspath(__file__)))# print(os.path.dirname(os.path.abspath(__file__)))# 执行命令,在命令行中是$ scrapy crawl jobboleexecute(['scrapy','crawl','jobbole'])
main.py

执行前需要修改settings文件

# Obey robots.txt rulesROBOTSTXT_OBEY = False     #需要修改为False,这个配置默认读取每个网站上的robots协议,爬取时须关闭

使用命令行在项目目录中执行命令:

$ scrapy crawl jobbole

可以看到程序正常执行

 

此时可以使用pycharm的debug模式,在jobbole.py文件中设置断点

点击debug运行程序后,在设置断点的位置将停止程序

选择request时,会浮动一个框,可以点击+号查看详情

这时可以看到返回的类型、url、body等内容,在最右边的view还可以看到详情

 

开始爬取

首先我们先选择一个页面:http://blog.jobbole.com/114329/

打开页面之后,检查,这时我们就能看到网页上的代码

注意:此时检查的代码与查看源文件的代码有可能不一致,是由于有些html代码是通过js渲染出来的,此时我们就需要针对源文件的代码进行查找,否则有可能出现找不到的情况。

我们在jobbole.py文件中进行编写xpath查找代码:

def parse(self, response):        # 通过XPATH查找这个‘h1’标签        re_selector = response.xpath('//*[@class="entry-header"]/h1')        # 可以通过text()的方法获取内部文本信息        re_selector_data = response.xpath('//*[@id="post-114329"]/div[1]/h1/text()')        # 获取title的标签的文本信息        title = response.xpath('//*[@class="entry-header"]/h1/text()')
View Code

 

由于如果经常这样调试,会反复进行网站请求,scrapy提供了一个shell的功能,可以在终端通过命令进行调试:

# 在终端中,可以通过这个命令,对网址进行请求scrapy shell http://blog.jobbole.com/114329/#然后在终端中会显示如下:[s] Available Scrapy objects:[s]   scrapy     scrapy module (contains scrapy.Request, scrapy.Selector, etc)[s]   crawler    
[s] item {}[s] request
[s] response <200 http://blog.jobbole.com/114329/>[s] settings
[s] spider
[s] Useful shortcuts:[s] fetch(url[, redirect=True]) Fetch URL and update local objects (by default, redirects are followed)[s] fetch(req) Fetch a scrapy.Request and update local objects [s] shelp() Shell help (print this help)[s] view(response) View response in a browser# 这其中,response对象就是我们获取的html返回
View Code

 

我们可以通过命令进行本地调试:

>>> title = response.xpath('//*[@class="entry-header"]/h1')>>> title[
]>>> title = response.xpath('//*[@class="entry-header"]/h1/text()')# 我们可以用extract()命令将selector对象转换为列表对象>>> title.extract()['如何在 Linux Shell 编程中定义和使用函数']# 从列表对象中取出的第一个值,就是我们希望得到的字符串>>> title.extract()[0]'如何在 Linux Shell 编程中定义和使用函数'# 获取p标签的内容>>> create_date = response.xpath('//*[@id="post-114329"]/div[2]/p/text()')# 此时我们看到,标签内容中有很多的换行符,我们可以通过strip()命令进行过滤>>> create_date.extract()['\r\n\r\n 2018/08/29 · ', '\r\n \r\n \r\n\r\n \r\n · ', '\r\n \r\n']# 过滤后的内容中还有一个‘.‘存在>>> create_date.extract()[0].strip()'2018/08/29 ·'# 我们可以将这个.进行替换操作>>> create_date.extract()[0].strip().replace('·','')'2018/08/29 '# 随后再进行一个strip()操作,即可得到一个理想的值>>> create_date.extract()[0].strip().replace('·','').strip()'2018/08/29'
View Code

 

 随后我们希望获取文章的点赞数量,我们查看了HTML代码,发现点赞数量的html代码如下:

我们使用如下代码,发现获取的列表为空,是因为xpath的class中,除了我们搜索的内容外,还有其他的内容

# 这个代码无法获取内容response.xpath('//span[@class="vote-post-up"]')

我们用contains语法进行获取,含义是:获取class中包含vote-post-up的标签

response.xpath('//span[contains(@class="vote-post-up")]')

随后我们再获取这个标签下面的h10标签的内容,进行extract,并获取列表的第一个值,此时获取到的是一个str类型的,由于希望获得的是数字类型的,我们再转换为数字类型的

vote =  int(response.xpath('//span[contains(@class,"vote-post-up")]/h10/text()').extract()[0])

获取点赞数量等

# 首先我们获取点赞的标签文本,文本内容如:【1 点赞】        faver_num = response.xpath('//span[contains(@class,"bookmark-btn")]/text()').extract()[0]#我们需要将数字信息提取出来,用正则表达式进行提取match_fav_re = re.match(".*(\d+).*",faver_num)#如果没人点赞的情况,我们默认设为0,如果有则取出,并转换为数字        if match_fav_re:            faver_num = int(match_fav_re.group(1))        else:            faver_num = 0

 

最后获取一下标签信息

tag_list = response.css('.entry-meta-hide-on-mobile a::text').extract()# 有可能获取到的列表中包含了不需要的项目如:【it技术,3评论,微世界】        # 列表去重,去掉以“评论”字符为结尾的所有项  tag_list = [element for element in tag_list if not element.strip().endswith("评论")]

 

除了通过xpath方式,还可以通过css方式进行标签获取

此时我们就针对每一个页面的内容获取完毕了,我们定义一个获取详情的函数

def parse_detail(self,response):    # 提取文章的具体字段    # re_selector_data = response.xpath('//*[@id="post-114329"]/div[1]/h1/text()')    title = response.xpath('//*[@class="entry-header"]/h1/text()').extract()[0]    create_date = response.css('.entry-meta .entry-meta-hide-on-mobile::text').extract()[0].strip().replace('·','').strip()    vote_num = int(response.xpath('//span[contains(@class,"vote-post-up")]/h10/text()').extract()[0])    faver_num = response.xpath('//span[contains(@class,"bookmark-btn")]/text()').extract()[0]    match_fav_re = re.match(".*(\d+).*",faver_num)    if match_fav_re:        faver_num = int(match_fav_re.group(1))    else:        faver_num = 0    comment_num = response.xpath('//a[@href="#article-comment"]/span/text()').extract()[0]    match_com_re = re.match(".*(\d+).*",comment_num)    if match_com_re:        comment_num = int(match_com_re.group(1))    else:        comment_num = 0    cotent = response.xpath('//div[@class="entry"]').extract()[0]    tag_list = response.css('.entry-meta-hide-on-mobile a::text').extract()    # 列表去重,去掉以评论字符为结尾的所有项    tag_list = [element for element in tag_list if not element.strip().endswith("评论")]    pass
parse_detail

 

在学习了itemload以后,可以对这里进行大幅修改,将获取的方式留下,处理方式则放在item中处理

1、首先在item.py文件中新建一个类继承ItemLoader类,就可以修改其中的配置

from scrapy.loader.processors import TakeFirstfrom scrapy.loader import ItemLoader# 自定义一个类,继承ItemLoader类,就可以修改其中的配置class ArticleItemLoader(ItemLoader):    # 自定义itemloader,修改默认取第一个值,全部变为str    default_output_processor = TakeFirst()
View Code

2、在jobbole.py文件中的类中新建

class JobboleSpider(scrapy.Spider):    name = 'jobbole'    allowed_domains = ['blog.jobbole.com']    start_urls = ['http://blog.jobbole.com/all-posts/']
def parse_detail(self, response):    # 提取文章的具体字段    # 将原来代码中的meta传来的url获取一下    front_image_url = response.meta.get('front_image_url', '')    # 通过item loader加载item,通过自定义的ArticleItemLoader类进行实例对象,传入两个参数,一个是item对象,一个是获取的response    item_loader = ArticleItemLoader(item=JobboleArticleItem(), response=response)    # item_loader.add_xpath()    item_loader.add_css('title', '.entry-header h1::text')    item_loader.add_css('create_date', '.entry-meta .entry-meta-hide-on-mobile::text')    item_loader.add_xpath('vote_num', '//span[contains(@class,"vote-post-up")]/h10/text()')    item_loader.add_xpath('faver_num', '//span[contains(@class,"bookmark-btn")]/text()')    item_loader.add_xpath('comment_num', '//a[@href="#article-comment"]/span/text()')    item_loader.add_css('tag_list', '.entry-meta-hide-on-mobile a::text')    item_loader.add_xpath('cotent', '//div[@class="entry"]')    item_loader.add_value('front_image_url', [front_image_url])    item_loader.add_value('url', response.url)    item_loader.add_value('url_object_id', get_md5(response.url))    # 调用默认的item方法以后,会将所有的值全部变为list形式    article_item = item_loader.load_item()    # yield之后,会传递到pipelines.py中,需要在settings中将pipelines生效,将系统已经生成好的ITEM_PIPELINES打开注释    yield article_item
parse_detail

3、设置itme.py,新建了很多的方法,将方法传入item中

# 测试函数,可以将值后面增加字符串def add_jobbole(value):    return value+'jobbole'# 将日期格式字符串格式化为日期格式def date_convert(value):    try:        create_date = datetime.datetime.strptime(value, "%Y/%m/%d").date()    except Exception as e:        create_date = datetime.datetime.now().date()    return create_date# 获取文字中的数字的函数def get_num(value):    match_re = re.match(".*(\d+).*", value)    if match_re:        num = int(match_re.group(1))    else:        num = 0    return num# 专门去掉标签中的'评论字样'def remove_comment_tags(value):    if '评论' in value:        return ''    else:        return value# 空函数,用于将值变为列表格式def return_value(value):    return value# 自定义一个类,继承ItemLoader类,就可以修改其中的配置class ArticleItemLoader(ItemLoader):    # 自定义itemloader,修改默认取第一个值,全部变为str    default_output_processor = TakeFirst()class JobboleArticleItem(scrapy.Item):    # scrapy只有一个Field类型    # title = scrapy.Field()              # 文章名称    # create_date = scrapy.Field()        # 创建日期    # url = scrapy.Field()                # 文章url    # url_object_id = scrapy.Field()      # url->md5    # front_image_url = scrapy.Field()    # 封面图片    # front_image_path = scrapy.Field()   # 封面图片存放路径    # vote_num = scrapy.Field()           # 点赞数    # faver_num = scrapy.Field()          # 收藏数    # comment_num = scrapy.Field()        # 评论数    # cotent = scrapy.Field()             # 文章正文html    # tag_list = scrapy.Field()           # 文章标签    title = scrapy.Field(        # 给title全部增加一个jobbole的结尾,甚至可以调用多个函数        #input_processor=MapCompose(add_jobbole, lambda x: x+'-Trunkslisa')    )              # 文章名称    create_date = scrapy.Field(        # 给时间格式进行转换        input_processor=MapCompose(date_convert),        # 将获取的值取第一个        # output_processor=TakeFirst()    )        # 创建日期    url = scrapy.Field()                # 文章url    url_object_id = scrapy.Field()      # url->md5    front_image_url = scrapy.Field(        # 下载文件时,系统需要数组形式的数据,调用一个空函数,重新赋值        output_processor=MapCompose(return_value),    )    # 封面图片    front_image_path = scrapy.Field()   # 封面图片存放路径    vote_num = scrapy.Field(        input_processor=MapCompose(get_num)    )           # 点赞数    faver_num = scrapy.Field(        input_processor=MapCompose(get_num)    )          # 收藏数    comment_num = scrapy.Field(        input_processor=MapCompose(get_num)    )        # 评论数    cotent = scrapy.Field()             # 文章正文html    tag_list = scrapy.Field(        # 由于获取的内容本身就是list,这时候使用默认的TakeFirst()就不是太合适了        # 这时使用scrapy提供的Join类        output_processor=Join(","),    )           # 文章标签
items.py

 

 

 

获取全部文章

1、获取文章列表页中的文章url 并scrapy下载后交给解析函数进行具体字段的获取2、获取下一页的url 并交给scrapy进行下载,下载完成后再交给parse函数

定义parse函数

from scrapy.http import Requestdef parse(self, response):    """    1、获取文章列表页中的文章url 并scrapy下载后交给解析函数进行具体字段的获取    2、获取下一页的url 并交给scrapy进行下载,下载完成后再交给parse函数    """    post_list = response.css("#archive .floated-thumb .post-thumb a::attr(href)").extract()    for plist in post_list:        # 如果页面的url没有全部,只能提取到url的部分,需要针对url做拼接操作        # Request(url=parse.urljoin(response.url, plist), callback=self.parse_detail)        yield Request(url=plist, callback=self.parse_detail)        # print(post_list)    # 提取下一页的url,并交给scrapy进行下载    # 同一个节点2个class标签,可以通过没有空格的方式指定    next_urls = response.css(".next.page-numbers::attr(href)").extract()[0]    if next_urls:        yield Request(url=next_urls, callback=self.parse)
View Code

 

再看一下列表页,我们还有可能希望获取每条文章前面的图片,并增加到request中一起发给detail中进行保存

 

 

图中可以看到图片的url与文章的url分别处于两个不同的div之中,scrapy可以进行再次筛选,并将image_url通过meta参数,按照字典的方式传入

post_node = response.css("#archive .floated-thumb")for plist in post_node:    image_url = plist.css('.post-thumb a img::attr(src)').extract()[0]    post_url = plist.css('.post-thumb a::attr(href)').extract()[0]    # 可以将image_url通过meta参数,按照字典的方式传入     # 如果页面的url没有全部,只能提取到url的部分,scrapy可以将response.url直接拼接进入;若已经有域名,则不生效    yield Request(url=post_url, callback=self.parse_detail, meta={
'front_image_url': image_url})

 通过debug方式可以看到,response中的meta里面,已经有front_image_url的值,现在我们在detail中按照字典方式将其取出

# 通过字典方式将meta中的front_image_url取出,通过get方式防止异常,并赋一个空值front_image_url = response.meta.get('front_image_url', '')

 

 

items.py

定义一个数据类

class JobboleArticleItem(scrapy.Item):    # scrapy只有一个Field类型    title = scrapy.Field()              # 文章名称    create_date = scrapy.Field()        # 创建日期    url = scrapy.Field()                # 文章url    url_object_id = scrapy.Field()      # url->md5    front_image_url = scrapy.Field()    # 封面图片    front_image_path = scrapy.Field()   # 封面图片存放路径    vote_num = scrapy.Field()           # 点赞数    faver_num = scrapy.Field()          # 收藏数    comment_num = scrapy.Field()        # 评论数    cotent = scrapy.Field()             # 文章正文html    tag_list = scrapy.Field()           # 文章标签
View Code

然后再jobbole.py中引入

from ArticleSpider.ArticleSpider.items import JobboleArticleItem

 在parse_detail中实例化item

# 实例化Item类article_item = JobboleArticleItem()# 给item设置的字段赋值article_item['title'] = titlearticle_item['create_date'] = create_datearticle_item['url'] = response.urlarticle_item['front_image_url'] = [front_image_url]article_item['vote_num'] = vote_numarticle_item['faver_num'] = faver_numarticle_item['comment_num'] = comment_numarticle_item['cotent'] = cotentarticle_item['tag_list'] = tag_list# 这是将获取到的url进行了md5的编码article_item['url_object_id'] = get_md5(response.url)# yield之后,会传递到pipelines.py中,需要在settings中将pipelines生效,将系统已经生成好的ITEM_PIPELINES打开注释yield article_item
View Code

建立一个文件处理md5编码的情况

import hashlibdef get_md5(url):    # 判断传入的url是否是str格式的(str格式的默认都是unicode编码),并用utf8进行编码,用来确保可以进行md5运算    if isinstance(url,str):        url = url.encode('utf8')    m = hashlib.md5()    m.update(url)    return m.hexdigest()
View Code

pipeline想要生效还需要在setting文件中进行设置

# 默认路径是注释掉的,打开注释ITEM_PIPELINES = {    'ArticleSpider.pipelines.ArticleSpiderPipeline': 300,    # 如果希望使用系统的功能对图片进行下载,需要加入这个pipelines,并设置一下pipylines的执行顺序,数字越小,越早执行     'scrapy.pipelines.images.ImagesPipeline': 1,}# 这里设置一下是哪个字段为下载图片的字段IMAGES_URLS_FIELD = "front_image_url"# 获取工程路径project_dir = os.path.abspath(os.path.dirname(__file__))# 加入图片保存路径IMAGES_STORE = os.path.join(project_dir, 'image')# 配置下载100*100以上的图片IMAGES_MIN_HEIGHT = 100IMAGES_MIN_WIDTH = 100
View Code

还可以对下载图片进行配置,这里我们主要是希望能获得图片文件的保存路径,并赋值给item中我们设定的字段

# 引入类from scrapy.pipelines.images import ImagesPipeline# 新建一个类,并继承系统的ImagesPipeline类class ArticleImagePipeline(ImagesPipeline):    """    自定义编写这个函数,可以通过results获取文件保存的路径    通过debug可以看到results是一个元组形式的,元组第一个值是True,第二个值是一个字典。    字典中有一个path的key,value是保存的地址    通过一个for循环,将value取出,这个value是一个列表形式的    """    def item_completed(self, results, item, info):        for ok, value in results:            img_file_path = value['path']        # 此时我们就可以将文件保存的路径保存在front_image_path中        item['front_image_path'] = img_file_path        # pipeline类必须要返回item        return item
View Code

 

自定义将结果保存为json文件

import codecs   # codecs与open类似,但是减少了很多的编码工作import json# 自定义保存json的pipelineclass JsonWithEncodingPipeline(object):    def __init__(self):        self.file = codecs.open('artcle.json','w',encoding='utf-8')    # 处理item的写入文件的主要函数    def process_item(self,item,spider):        lines = json.dumps(dict(item), ensure_ascii=False) + "\n"        self.file.write(lines)        return item    # 定义一个关闭文件的函数    def spider_closed(self,spider):        self.file.close()
View Code

使用scrapy自带的json文件保存模块

# 调用scrapy提供的JsonExporter导出json文件class JsonExporterPipeline(object):    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
View Code

 

将结果保存在mysql中,首先需要在mysql中新建一个表,并设置好字段

# 需要在虚拟环境安装 mysqlclient# pip3 install mysqlclientimport MySQLdbclass MysqlPipeline(object):    def __init__(self):        # self.conn = MySQLdb.connect('host','user','password','dbname',charset="utf8",use_unicode=True)        self.conn = MySQLdb.connect('127.0.0.1','root','123','article_spider',charset="utf8",use_unicode=True)        self.cursor = self.conn.cursor()    def process_item(self,item,spider):        insert_sql = "insert into jobbole(title,create_date,url,url_object_id,front_image_url,front_image_path,vote_num,faver_num,comment_num,cotent) values (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)"        sql = self.cursor.execute(insert_sql, (            item['title'],            item['create_date'],            item['url'],            item['url_object_id'],            item['front_image_url'],            item['front_image_path'],            item['vote_num'],            item['faver_num'],            item['comment_num'],            item['cotent'],            # 这里本来希望把标签也进行导入,但是实际操作过程中报错了,就先注释掉了            # item['tag_list']        ))        self.conn.commit()
View Code

 

转载于:https://www.cnblogs.com/trunkslisa/p/9555927.html

你可能感兴趣的文章
mysql常用语法
查看>>
Morris ajax
查看>>
【Docker学习笔记(四)】通过Nginx镜像快速搭建静态网站
查看>>
ORA-12514: TNS: 监听程序当前无法识别连接描述符中请求的服务
查看>>
<转>云主机配置OpenStack使用spice的方法
查看>>
java jvm GC 各个区内存参数设置
查看>>
[使用帮助] PHPCMS V9内容模块PC标签调用说明
查看>>
基于RBAC权限管理
查看>>
数学公式的英语读法
查看>>
留德十年
查看>>
迷人的卡耐基说话术
查看>>
PHP导出table为xls出现乱码解决方法
查看>>
PHP问题 —— 丢失SESSION
查看>>
Java中Object类的equals()和hashCode()方法深入解析
查看>>
数据库
查看>>
dojo.mixin(混合进)、dojo.extend、dojo.declare
查看>>
Python 数据类型
查看>>
iOS--环信集成并修改头像和昵称(需要自己的服务器)
查看>>
PHP版微信权限验证配置,音频文件下载,FFmpeg转码,上传OSS和删除转存服务器本地文件...
查看>>
教程前言 - 回归宣言
查看>>