首页 > 编程语言 > 详细

python爬虫scrapy框架及例子

时间:2020-01-01 21:26:23      阅读:121      评论:0      收藏:0      [点我收藏+]

Scrapy是用python实现的一个为了爬取网站数据,提取结构性数据而编写的应用框架。使用Twisted高效异步网络框架来处理网络通信。

Scrapy架构:

技术分享图片

 

ScrapyEngine:引擎。负责控制数据流在系统中所有组件中流动,并在相应动作发生时触发事件。 此组件相当于爬虫的“大脑”,是 整个爬虫的调度中心。 

Schedule:调度器。接收从引擎发过来的requests,并将他们入队。初始爬取url和后续在页面里爬到的待爬取url放入调度器中,等待被爬取。调度器会自动去掉重复的url。

Downloader:下载器。负责获取页面数据,并提供给引擎,而后提供给spider。

 

Spider:爬虫。用户编些用于分析response并提取item和额外跟进的url。将额外跟进的url提交给ScrapyEngine,加入到Schedule中。将每个spider负责处理一个特定(或 一些)网站。 

ItemPipeline:负责处理被spider提取出来的item。当页面被爬虫解析所需的数据存入Item后,将被发送到Pipeline,并经过设置好次序

DownloaderMiddlewares:下载中间件。是在引擎和下载器之间的特定钩子(specific hook),处理它们之间的请求(request)和响应(response)。提供了一个简单的机制,通过插入自定义代码来扩展Scrapy功能。通过设置DownloaderMiddlewares来实现爬虫自动更换user-agent,IP等。

SpiderMiddlewares:Spider中间件。是在引擎和Spider之间的特定钩子(specific hook),处理spider的输入(response)和输出(items或requests)。提供了同样简单机制,通过插入自定义代码来扩展Scrapy功能。

 

数据流:

1.ScrapyEngine打开一个网站,找到处理该网站的Spider,并向该Spider请求第一个(批)要爬取的url(s);

2.ScrapyEngine向调度器请求第一个要爬取的url,并加入到Schedule作为请求以备调度;

3.ScrapyEngine向调度器请求下一个要爬取的url;

4.Schedule返回下一个要爬取的url给ScrapyEngine,ScrapyEngine通过DownloaderMiddlewares将url转发给Downloader;

5.页面下载完毕,Downloader生成一个页面的Response,通过DownloaderMiddlewares发送给ScrapyEngine;

6.ScrapyEngine从Downloader中接收到Response,通过SpiderMiddlewares发送给Spider处理;

7.Spider处理Response并返回提取到的Item以及新的Request给ScrapyEngine;

8.ScrapyEngine将Spider返回的Item交给ItemPipeline,将Spider返回的Request交给Schedule进行从第二步开始的重复操作,直到调度器中没有待处理的Request,ScrapyEngine关闭。

 

安装scrapy:

1.安装wheel支持:
$ pip install wheel

2.安装scrapy框架:

$ pip install scrapy

3.window下,为了避免windows编译安装twisted依赖,安装下面的二进制包 

$ pip install Twisted-18.4.0-cp35-cp35m-win_amd64.whl

 

scrapy项目结构:
在某路径下创建scrapy项目: $ scrapy startproject my_project

会产生以下目录和文件:

技术分享图片

 

外部的first目录:整个项目目录
scrapy.cfg:必须有的重要的项目的配置文件

内部的first目录:整个项目的全局目录

item.py:定义Item类,从scrapy.Item继承,里面定义scrapy.Field类

pipelines.py:处理爬取的数据流向。重要的是process_item()方法

first目录下的__init__.py:作为包文件必须有的文件

spiders目录下的__init__.py:也是必须有。在这里可以写爬虫类或爬虫子模块

settings.py:

BOT_NAME  # 爬虫名

ROBOTSTXT_OBEY = True  # 遵守robots协议

USER_AGENT=‘‘  # 指定爬取时使用。一定要更改user-agent,否则访问会报403错误

CONCURRENT_REQUEST = 16  # 默认16个并行

DOWNLOAD_DELAY = 3  # 下载延时

COOKIES_ENABLED = False  # 缺省是启用。一般需要登录时才需要开启cookie

DEFAULT_REQUEST_HEADERS = {}  # 默认请求头,需要时填写

SPIDER_MIDDLEWARES  # 爬虫中间件

DOWNLOADER_MIDDLEWARES  # 下载中间件

‘first.middlewares.FirstDownloaderMiddleware‘: 543  # 543优先级越小越高

‘firstscrapy.pipelines.FirstscrapyPipeline‘: 300  # item交给哪一个管道处理,300优先级越小越高

  

豆瓣书评爬取:

创建爬虫代码模版

命令:scrapy genspider -t basic book douban.com  # book是爬虫名字;douban.com是要爬取的url的域名

模板如下:

技术分享图片
# -*- coding: utf-8 -*-
import scrapy


class BookSpider(scrapy.Spider):
    name = book
    allowed_domains = [douban.com]
    start_urls = [http://douban.com/]

    def parse(self, response):
        pass
技术分享图片

 此时就已经成功创建一个名为‘book’的爬虫,可以通过命令scrapy list查看。

 

response是服务器端HTTP响应,它是scrapy.http.response.html.HtmlResponse类。由此,修改代码如下 :

技术分享图片
# -*- coding: utf-8 -*-
import scrapy
from scrapy.http.response.html import HtmlResponse

class BookSpider(scrapy.Spider):
    name = book  # 爬虫名
    allowed_domains = [douban.com]  # 域名。爬虫爬取范围
    start_urls = [https://book.douban.com/tag/%E7%BC%96%E7%A8%8B?start=0&type=T]  # 起始url,从第一页开始爬取

    # 下载器获取WebServer的response,parse就是解析响应response的内容
    def parse(self, response: HtmlResponse):  # 如何解析html;返回一个可迭代对象:利用yiled
        print(type(response))  # scrapy.http.response.html.HtmlResponse
        print(type(response.text))  # str
        print(type(response.body))  # bytes
        print(response.encoding)  # utf-8
        # 将网页内容写入book.html文件内
        with open(/Users/dannihong/Documents/leetcode/scrapy_project/file/book.html, w, encoding=utf-8) as f:
            f.write(response.text)
            f.flush()
        except Exception as e:
            print(e)
技术分享图片

 

爬虫获得的内容response对象,可以使用解析库来解析。scrapy包装了lxml,父类TextResponse类也提供了xpath方法和css方法,可以混合使用这两套接口解析HTML。解析html页面内容的示例代码如下:

技术分享图片
# -*- coding: utf-8 -*-
from scrapy.http.response.html import HtmlResponse

response = HtmlResponse(file:/Users/dannihong/Documents/leetcode/scrapy_project/file/book.html, encoding=utf-8)
with open(/Users/dannihong/Documents/leetcode/scrapy_project/file/book.html, encoding=utf8) as f:
    response._set_body(f.read().encode())  # _set_body方法将其放入response对象里;需要传入的参数对象是bytes,所以encode()
subjects = response.css(li.subject-item)
for subject in subjects:
    # 提取书籍的网页链接
    href = subject.xpath(.//h2).css(a::attr(href)).extract()
    print(href:, href[0])
    # 使用正则表达式,选取评分是9分以上的书籍
    rate = subject.xpath(.//span[@class="rating_nums"]/text()).re(r^9.*)
    # rate = subject.css(‘span.rating_nums::text‘).re(r‘^9\..*‘)  # 第二种表达方式
    if rate:
        print(rate[0])
技术分享图片

 

item封装数据:

# first/item.py

技术分享图片
import scrapy

class Test1ProItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    title = scrapy.Field()  # 存放书籍标题的字段
    rate = scrapy.Field()  # 存放书籍评分的字段
技术分享图片

 

 

# first/first/spiders/book.py

技术分享图片
# -*- coding: utf-8 -*-
import scrapy
from scrapy.http.response.html import HtmlResponse
from ..items import FirstItem  # 从上一层的items.py文件里导入

class BookSpider(scrapy.Spider):
    name = book  # 爬虫名
    allowed_domains = [douban.com]  # 爬虫爬取范围
    start_urls = [https://book.douban.com/tag/%E7%BC%96%E7%A8%8B?start=0&type=T]  # 起始url
    custom_settings = {file_name: /Users/dannihong/Documents/leetcode/scrapy_project/file/books.json}  # 一般设置参数
 
    # 下载器获取WebServer的response,parse解析响应的内容;输出items和requests
    def parse(self, response: HtmlResponse):  # 如何解析html;返回一个可迭代对象:利用yiled
        subjects = response.xpath(//li[@class="subject-item"])
        items = []  # 如果用items=[],最后函数要return items
        for subject in subjects:
            item = FirstItem()  # 声明一个item,相当于一个字典,存放要爬取的数据

            title = subject.xpath(.//h2/a/text()).extract()
            item[title] = title[0].strip()
            rate = subject.css(span.rating_nums::text).extract()
            item[rate] = rate[0].strip()

            items.append(item)

        with open(book.json, w, encoding=utf8) as f:
            for item in items:
                f.write({} {}\n.format(item[title], item[rate]))

        return items
技术分享图片

 

pipeline处理

将book.py中BookSpider改成生成器,只需要把return items改造成yield item,即由产生一个列表变成yield一个个item。脚手架帮我们创建了一个pipelines.py文件和一个类。 

# Configure item pipelines
# See https://doc.scrapy.org/en/latest/topics/item-pipeline.html
ITEM_PIPELINES = {
   first.pipelines.FirstPipeline: 300,
}

整数300表示优先级,越小越高。取值范围为0-1000。 

 pipeline.py里常用的方法:

process_item(self, item, spider)  # item表示爬取的一个个数据,spider表示item的爬取者,每一个item处理都得调用。返回一个item对象,或者抛出DropItem异常,被丢弃的item对象将不会被pipeline组件处理;

open_spider(self, spider)  # spider表示被开启的spider,调用一次

close_spider(self, spider)  # spider表示被关闭的spider,调用一次

__init__(self)  # spider创建实例时调用一次

 

将爬取的数据通过pipeline写入到json文件中,代码如下:

# first/spiders/book.py

技术分享图片
# -*- coding: utf-8 -*-
import scrapy
from scrapy.http.response.html import HtmlResponse
from ..items import Test1ProItem

class BookSpider(scrapy.Spider):
    name = book
    allowed_domains = [douban.com]
    start_urls = [https://book.douban.com/tag/%E7%BC%96%E7%A8%8B?start=0&type=T]
    custom_settings = {file_name: /Users/dannihong/Documents/leetcode/scrapy_project/file/books.json}  # spider上自定义配置信息

    def parse(self, response: HtmlResponse):  # 如何解析html;返回一个可迭代对象:利用yiled
        subjects = response.xpath(//li[@class="subject-item"])
        for subject in subjects:
            item =FirstProItem()
            title = subject.xpath(.//h2/a/text()).extract()
            item[title] = title[0].strip()
            rate = subject.css(span.rating_nums::text).extract()
            item[rate] = rate[0].strip()
            yield item  # 返回一个可迭代对象生成器
技术分享图片

 

# first/pipelines.py

技术分享图片
# -*- coding: utf-8 -*-

# Define your item pipelines here
#
# Don‘t forget to add your pipeline to the ITEM_PIPELINES setting
# See: https://doc.scrapy.org/en/latest/topics/item-pipeline.html
import json
from scrapy import Spider

class Test1ProPipeline(object):
    def __int__(self):
        print(~~~~~~~~~~init~~~~~~~~~~)

    # 每一个item都会执行一次
    def process_item(self, item, spider:Spider):
        print(++++++++++)
        print(item)
        self.file.write({},\n.format(json.dumps(dict(item))))
        return item

    # 所有过程在起始的时候执行一次
    def open_spider(self, spider):
        print(==========open spider {}==========.format(spider))
        # file_name = ‘/Users/dannihong/Documents/leetcode/scrapy_project/file/books.json‘
        file_name = spider.settings[file_name]
        self.file = open(file_name, w, encoding=utf-8)
        self.file.write([\n)

    # 所有过程结束的时候执行一次
    def close_spider(self, spider):
        print(==========close spider {}==========.format(spider))
        self.file.write(])
        self.file.close()
技术分享图片

 

 

Scrapy简单介绍及爬取伯乐在线所有文章

一.简说安装相关环境及依赖包

  1.安装Python(2或3都行,我这里用的是3)

  2.虚拟环境搭建:

    依赖包:virtualenv,virtualenvwrapper(为了更方便管理和使用虚拟环境)

    安装:pip install virtulaenv,virtualenvwrapper或通过源码包安装

    常用命令:mkvirtualenv --python=/usr/local/python3.5.3/bin/python article_spider(若有多个Python版本可以指定,然后创建虚拟环境article_spider);

         workon :显示当前环境下所有虚拟环境

技术分享图片

        workon 虚拟环境名:进入相关环境:

技术分享图片

        退出虚拟环境:deactivate

        删除虚拟环境:rmvirtualenv article_spider

        

    安装相关依赖包及Scrapy框架:pip install scrapy(建议用豆瓣源镜像安装,快得多pip install https://pypi.douban.com /simple scrapy)   

                  windows操作环境中还需安装(pip install pypiwin32)

                                                                 注:若安装失败有可能是版本不同,可以到官网查看对应版本安装:https://www.lfd.uci.edu/~gohlke/pythonlibs/

  3.新建Scrapy项目(可以定制模板,这里用默认的):

    scrapy startproject article_spider:  

技术分享图片

    用Pycharm打开,结构如下(与Django相似),爬虫都放在spider文件夹中:

技术分享图片

    创建爬虫文件:cd article_spider:进入项目

           scrapy genspider --list(查看spider提供的模板)

技术分享图片

           scrapy genspider -t 模板名 爬虫文件名 域名(指定模板):

技术分享图片

 

 

           scrapy genspider 爬虫文件名  所爬取的域名(默认模板为basic)

技术分享图片

 

技术分享图片

技术分享图片

    jobbole.py文件如下(start_url中的url都会通过parse函数,可以把要爬取的网址放进start_url):查看Spider源码可知,通过start_requests返回url,是一个生成器

技术分享图片
# -*- coding: utf-8 -*-
import scrapy


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

    def parse(self, response):
        pass
技术分享图片

 

二.爬虫相关技能介绍

  1.新建main函数,执行并调试爬虫:

技术分享图片
from scrapy.cmdline import execute
import sys
import os
#将父目录添加到搜索目录中
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
execute(["scrapy","crawl","jobbole"])
技术分享图片

 

  修改setting.py:

# Obey robots.txt rules
#默认为True会过滤掉ROBOTS协议
ROBOTSTXT_OBEY =False

   调试结果如下,body中为网页所有内容:

技术分享图片

  

  3.scrapy shell的使用(方便调试):

    3.1scrapy shell  "http://blog.jobbole.com/114405/"(scrapy shell 要爬取调试的url)

 技术分享图片

    3.2xpath提取并获取文章名,extract()方法将Selectorlist转换为list:

技术分享图片

  3.Xpath的使用,提取所需内容(比Beautifulsoup快得多):

    3.1xapth节点关系:

      父节点

      子节点

      同胞节点(兄弟节点)

      先辈节点

      后代节点

    3.2xpath语法简单使用:

技术分享图片

技术分享图片

技术分享图片

    3.3提取文章名:

技术分享图片
# -*- coding: utf-8 -*-
import scrapy


class JobboleSpider(scrapy.Spider):
    name = jobbole
    allowed_domains = [blog.jobbole.com]
    start_urls = [http://blog.jobbole.com/114405/‘]
def parse(self, response):
   title
=response.xpath(//*[@id="post-114405"]/div[1]/h1/text())
  
pass
技术分享图片

 

技术分享图片

 

返回的是一个Selectorlist,便于嵌套xpath

  

    3.4xpath获取时间:

技术分享图片

    3.5获取点赞数,xpath的contains函数,获取class包含vote-post-up的span标签下的:

技术分享图片

    3.6获取收藏数:

技术分享图片
fav_nums=response.xpath("//span[contains(@class,‘bookmark-btn‘)]/text()").extract()[0]
#使用正则匹配,有可能无收藏,匹配不到 match_fav
=re.match(".*(\d+).*",fav_nums) if match_fav: fav_nums=int(match_fav.group(1)) else: fav_nums=0
技术分享图片

 

     3.7获取评论数:

技术分享图片
comments_nums = response.xpath("//a[@href=‘#article-comment‘]/span/text()").extract()[0]
        math_comments=re.match(".*(\d).*",comments_nums)
        if math_comments:
            comments_nums=int(math_comments)
        else:
            comments_nums=0
技术分享图片

 

     3.8文章内容:

技术分享图片

 

     3.9标签提取:

 tag_list= response.xpath(//*[@id="post-114405"]/div[2]/p/a/text()).extract()
 tag_list=[element for element in tag_list if not element.strip().endswith("评论")]
 tags=,.join(tag_list)

 

  4.CSS选择器筛选提取内容:

     4.1CSS常用方法:

技术分享图片

技术分享图片

技术分享图片

    4.2获取文章名:

技术分享图片

 

    

    4.3获取时间:

技术分享图片

 

    4.4获取点赞数:

 技术分享图片

 

    4.5获取收藏数:

 技术分享图片

 

     4.6获取评论数:

 技术分享图片

 

     4.7文章内容:

技术分享图片 

     4.8标签提取:

技术分享图片

 

  5.Xpath和CSS提取比较,哪种方便用哪个都行,extract()[0]可以换成extract_first("")直接提取第一个,无则返回空:

技术分享图片
# -*- coding: utf-8 -*-
import scrapy
import re

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

    def parse(self, response):
        #通过xpath提取
        title=response.xpath(‘//div[@class="entry-header"]/h1/text()‘)
        create_date= response.xpath(‘//p[@class="entry-meta-hide-on-mobile"]/text()‘).extract()[0].replace("·","").strip()
        praise_nums=response.xpath("//span[contains(@class,‘vote-post-up‘)]/h10/text()").extract()[0]
        if praise_nums:
            praise_nums=int(praise_nums)
        else:
            praise_nums=0
        fav_nums=response.xpath("//span[contains(@class,‘bookmark-btn‘)]/text()").extract()[0]
        match_fav=re.match(".*(\d+).*",fav_nums)
        if match_fav:
            fav_nums=int(match_fav.group(1))
        else:
            fav_nums=0
        comments_nums = response.xpath("//a[@href=‘#article-comment‘]/span/text()").extract()[0]
        math_comments=re.match(".*(\d).*",comments_nums)
        if math_comments:
            comments_nums=int(math_comments.group(1))
        else:
            comments_nums=0
        cotent=response.xpath(‘//div[@class="entry"]‘).extract()[0]
        tag_list= response.xpath(‘//div[@class="entry-meta"]/p/a/text()‘).extract()
        tag_list=[element for element in tag_list if not element.strip().endswith("评论")]
        tags=‘,‘.join(tag_list)

        #通过CSS提取
        title=response.css(".entry-header > h1::text").extract()[0]
        create_time=response.css("p.entry-meta-hide-on-mobile::text").extract()[0].replace("·","").strip()
        praise_nums=int(response.css("span.vote-post-up h10::text").extract()[0])
        if praise_nums:
            praise_nums = int(praise_nums)
        else:
            praise_nums = 0
        fav_nums=response.css(".bookmark-btn::text").extract()[0]
        match_fav = re.match(".*(\d+).*", fav_nums)
        if match_fav:
            fav_nums = int(match_fav.group(1))
        else:
            fav_nums = 0
        comments_nums=response.css("a[href=‘#article-comment‘] span::text").extract()[0]
        math_comments = re.match(".*(\d).*", comments_nums)
        if math_comments:
            comments_nums = int(math_comments.group(1))
        else:
            comments_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)
技术分享图片

 

 三.具体实现

  1.获取所有文章url(jobbole.py):

技术分享图片
from scrapy.http import Request
#提取域名的函数
#python3
from urllib import parse
#python2
#import urlparse

class JobboleSpider(scrapy.Spider):
    name = jobbole
    allowed_domains = [blog.jobbole.com]
    start_urls = [http://blog.jobbole.com/all-posts/]

    def parse(self, response):
        ‘‘‘
        1.获取文章列表页中的文章url并交给scrapy下载后进行解析;
        2.获取下一页url并交给scrapy下载交给parse解析字段  
        ‘‘‘
        #解析列表页中所有文章url交给scrapy下载后进行解析
        post_urls=response.css("div#archive div.floated-thumb div.post-meta p a.archive-title::attr(href)").extract()
        for post_url in post_urls:
            #若提取的url不全,不包含域名,可以用parse拼接
            #Request(url=parse.urljoin(response.url,post_url),callback=self.parse_detail)
            #生成器,回调
            yield Request(post_url,callback=self.parse_detail)
        #提取下一页并交给scrapy下载
        next_url=response.css(".next.page-numbers::attr(href)").extract_first()
        if next_url:
            yield Request(next_url,callback=self.parse)

    def parse_detail(self,response):
        # 通过xpath提取
        title=response.xpath(//div[@class="entry-header"]/h1/text())
        create_date= response.xpath(//p[@class="entry-meta-hide-on-mobile"]/text()).extract()[0].replace("·","").strip()
        praise_nums=response.xpath("//span[contains(@class,‘vote-post-up‘)]/h10/text()").extract()[0]
        if praise_nums:
            praise_nums=int(praise_nums)
        else:
            praise_nums=0
        fav_nums=response.xpath("//span[contains(@class,‘bookmark-btn‘)]/text()").extract()[0]
        match_fav=re.match(".*(\d+).*",fav_nums)
        if match_fav:
            fav_nums=int(match_fav.group(1))
        else:
            fav_nums=0
        comments_nums = response.xpath("//a[@href=‘#article-comment‘]/span/text()").extract()[0]
        math_comments=re.match(".*(\d).*",comments_nums)
        if math_comments:
            comments_nums=int(math_comments.group(1))
        else:
            comments_nums=0
        cotent=response.xpath(//div[@class="entry"]).extract()[0]
        tag_list= response.xpath(‘//div[@class="entry-meta"]/p/a/text()).extract()
        tag_list=[element for element in tag_list if not element.strip().endswith("评论")]
        tags=,.join(tag_list)
技术分享图片

   2.获取文章封面图:

技术分享图片
class JobboleSpider(scrapy.Spider):
    name = jobbole
    allowed_domains = [blog.jobbole.com]
    start_urls = [http://blog.jobbole.com/all-posts/]

    def parse(self, response):
        ‘‘‘
        1.获取文章列表页中的文章url并交给scrapy下载后进行解析;
        2.获取下一页url并交给scrapy下载交给parse解析字段  
        ‘‘‘
        #解析列表页中所有文章url交给scrapy下载后进行解析
        #获取url及image的节点
        post_nodes=response.css("div#archive div.floated-thumb div.post-thumb a")
        for post_node in post_nodes:
            image_url=post_node.css("img::attr(src)")
            post_url=post_node.css("::attr(href)")
            #若提取的url不全,不包含域名,可以用parse拼接
            #Request(url=parse.urljoin(response.url,post_url),callback=self.parse_detail)
            #生成器,回调
            yield Request(parse.urljoin(response.url,post_url),meta={"front-image-url":image_url},callback=self.parse_detail)
        #提取下一页并交给scrapy下载
        next_url=response.css(".next.page-numbers::attr(href)").extract_first()
        if next_url:
            yield Request(next_url,callback=self.parse)
  def parse_detail(self,response):
  # 通过xpath提取
   front_image_url=response.meta.get("front-image-url","")#文章封面图
技术分享图片

 

  2.items.py(通过item实例化,类似于字典,但功能更全,可以集中管理,去重等):

    2.1源码如下:

技术分享图片
# -*- coding: utf-8 -*-

# Define here the models for your scraped items
#
# See documentation in:
# https://doc.scrapy.org/en/latest/topics/items.html

import scrapy


class ArticleSpiderItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    pass
技术分享图片

 

     2.2添加文章类,用来实例化提取的文章格式(类似于Django的Model)【items.py中】:

技术分享图片
......
class JobboleArticleSpider(scrapy.Item):
    #字段中有Field类型,可以接受任何类型
    title=scrapy.Field()
    create_date=scrapy.Field()
    url=scrapy.Field()
    #对url做MD5,固定url的长度
    url_object_id=scrapy.Field()
    front_image_url=scrapy.Field()
    front_image_path=scrapy.Field()
    praise_nums=scrapy.Field()
    fav_nums=scrapy.Field()
    comment_nums=scrapy.Field()
    tags=scrapy.Field()
    content=scrapy.Field()
技术分享图片

 

    2.3填充数据(jobbole.py中):

技术分享图片
......  

def parse_detail(self,response): #实例化item中JobboleArtilce对象 article_item=JobboleArticleSpider() # 通过xpath提取 front_image_url=response.meta.get("front-image-url","")#文章封面图 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].replace("·","").strip() praise_nums=response.xpath("//span[contains(@class,‘vote-post-up‘)]/h10/text()").extract()[0] if praise_nums: praise_nums=int(praise_nums) else: praise_nums=0 fav_nums=response.xpath("//span[contains(@class,‘bookmark-btn‘)]/text()").extract()[0] match_fav=re.match(".*(\d+).*",fav_nums) if match_fav: fav_nums=int(match_fav.group(1)) else: fav_nums=0 comments_nums = response.xpath("//a[@href=‘#article-comment‘]/span/text()").extract()[0] math_comments=re.match(".*(\d).*",comments_nums) if math_comments: comments_nums=int(math_comments.group(1)) else: comments_nums=0 content=response.xpath(//div[@class="entry"]).extract()[0] tag_list= response.xpath(//div[@class="entry-meta"]/p/a/text()).extract() tag_list=[element for element in tag_list if not element.strip().endswith("评论")] tags=,.join(tag_list) #填充item数据 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["fav_nums"]=fav_nums article_item["comment_nums"]=comments_nums article_item["tags"]=tags article_item["content"]=content #传递到item中 yield article_item
技术分享图片

 

    2.4把setting.py中ITEM_PIPELINES注释取消,让其生效:

ITEM_PIPELINES = {
   article_spider.pipelines.ArticleSpiderPipeline: 300,
}

     

技术分享图片

调试发现,数据会传送到pipelines中,可以在这儿做一系列操作

    2.5在setting.py中添加ImagesPipelines实现图片自动下载(virtualenvs\article_spider\Lib\site-packages\scrapy\pipelines\images.py),下载图片需要依赖PIL这个库(pip install pillow):

技术分享图片

技术分享图片
ITEM_PIPELINES = {
   article_spider.pipelines.ArticleSpiderPipeline: 300,
   #后面的数字大小表示pipeline先后流经的顺序,先1,后300
   ‘scrapy.pipelines.images.ImagesPipeline‘:1
}
#配置图片在item中下载的字段,front-image-url会被当成数组处理,因此jobole中该字段应改成列表
IMAGES_URLS_FIELD="front_image_url"
#配置图片保存路径
import os
#获取setting.py的父目录
project_dir=os.path.dirname(os.path.abspath(__file__))
IMAGES_STORE=os.path.join(project_dir,"images")
#设置图片最小宽度,高度,即必须大于这么多才下载,还有很多属性可以坎源码
IMAGES_MIN_HEIGHT=100
IMAGES_MIN_WIDTH=100
 
技术分享图片

 

 技术分享图片

front_image_url需转换为列表格式才能被下载

技术分享图片

部分image.py源码 

    2.6定制pipeline处理封面图,获取图片地址(pipelines.py中):

 

技术分享图片
class ArticleImagePipeline(ImagesPipeline):
    ‘‘‘
    定制图片的pipepline
    ‘‘‘
   #重写ImagesPipeline中的item_completed()函数
    def item_completed(self, results, item, info):
        for ok, value in results:
            image_path = value[path]
        item[front_image_path] = image_path
    #记得返回item
return item
技术分享图片

 

 技术分享图片

 通过item_completed()获取的results结构如上图技术分享图片

item_completed()该方法源码如上图

    2.7使用md5固定url长度(Python3默认所有字符为unicode,而unicode不能被hash),可以单独写在一个py文件;里,新建utils下common.py:

技术分享图片
import hashlib


def get_md5(url):
    # 判断url如果为unicode编码,则转换为utf-8
    if isinstance(url, str):
        url = url.encode(utf-8)
    m = hashlib.md5()
    m.update(url)
    return m.hexdigest()


if __name__ == "__main__":
    print(get_md5("https://jobbole.com".encode(utf-8)))
技术分享图片

 

 技术分享图片

导入模块并调用get_md5()填充数据(jobbole.py)

  3.数据的保存:

    3.1通过json格式保存到文件(PIpelines.py,记得配置到setting中):

技术分享图片
class JsonEncodingPipeline(object):
#自定义json文件的导出
def __init__(self): self.file=codecs.open("article.json","w",encoding=utf-8) def process_item(self, item, spider): #调用pipeline生成的函数,ensure_ascii=False防止中文等编码错误 lines=json.dumps(dict(item),ensure_ascii=False)+"\n" self.file.write(lines) return item def spider_closed(self,spider): ‘‘‘ 调用spider_closed(信号)关闭文件 ‘‘‘ self.file.close()
技术分享图片

 

技术分享图片
ITEM_PIPELINES = {
   article_spider.pipelines.ArticleSpiderPipeline: 300,
   #后面的数字大小表示pipeline先后流经的顺序
   # ‘scrapy.pipelines.images.ImagesPipeline‘:1
   ‘article_spider.pipelines.JsonEncodingPipeline‘:2,
   article_spider.pipelines.ArticleImagePipeline:1
}
技术分享图片

 

    3.2还可以用scrapy自带的库保存json格式(还有很多其他格式)(from scrapy.exporters import JsonItemExporter):

 

技术分享图片

exporters中的文件格式种类

技术分享图片
class JsonExporterPipeline(object):
    #调用scrapy提供的exporter导出json文件
    def __init__(self):
        self.file=open("articlexporter.json","wb")
        self.exporter=JsonItemExporter(self.file,encoding="utf-8",ensure_ascii=False)
#写入"[\n" self.exporter.start_exporting()
def close_spider(self,spider):
    #写入"]\n" self.exporter.finish_exporting() self.file.close()
def process_item(self, item, spider): self.exporter.export_item(item) return item
技术分享图片

 

技术分享图片

    3.3数据导入mysql:

      表的设计,这里只有一张表,可以直接设计:

技术分享图片

 

      时间格式的转换:

 try:
       create_date=datetime.datetime.strptime(create_date,"%Y/%m/%d").date()
 except Exception as e:
       create_date=datetime.datetime.now().date()
 article_item["create_date"]=create_date

       mysql驱动安装:pip install mysqlclient,利用Mysqldb连接数据库时的参数:

技术分享图片

 

       mysqlpipeline的实现,第一种插入mysql的方法(记得setting中配置pipeline),解析速度大于数据插入mysql速度,有可能导致阻塞:

 

技术分享图片
......
import Mysqldb
class
MysqlPipeline(object): #数据导入数据库,爬取速度有可能远大于插入速度,造成阻塞 def __init__(self): self.conn=MySQLdb.connect("localhost","root","112358","bole_articles",charset="utf8",use_unicode=True) self.cursor=self.conn.cursor() def process_item(self,item,spider): inser_sql=""" insert into articles(title,url,url_object_id,font_img_url,font_img_path,create_time,fa_num,sc_num,pinglun_num,tag,content) VALUES(%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) """ self.cursor.execute(inser_sql,(item["title"],item["url"],item["url_object_id"], item["front_image_url"],item["front_image_path"],item["create_date"], item["praise_nums"],item["fav_nums"],item["comment_nums"],item["tags"],item["content"])) self.conn.commit()
技术分享图片

 

     mysqltwistedpipeline异步插入(基于Twisted异步框架):

   

#setting中配置mysql相关信息
MYSQL_HOST="localhost"
MYSQL_DBNAME="bole_articles"
MYSQL_USER="root"
MYSQL_PASSWORD="112358"

 

     

技术分享图片
......
from twisted.enterprise import adbapi
import MySQLdb
import MySQLdb.cursors
class MysqlTwistedPipeline(object):
    def __init__(self, dbpool,dbpool2):
        self.dbpool = dbpool

    # 导入setting中的配置(固定函数),注:是from_settings而不是from_setting
    @classmethod
    def from_settings(cls, setting):
        # 将dbtool传入
        dbparms = dict(
            host=setting["MYSQL_HOST"],
            db=setting["MYSQL_DBNAME"],
            user=setting["MYSQL_USER"],
            password=setting["MYSQL_PASSWORD"],
            charset="utf8",
            cursorclass=MySQLdb.cursors.DictCursor,
            use_unicode=True,
        )
        # twisted异步容器,使用MySQldb模块连接
        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):
        # 执行具体的插入
        inser_sql = """
               insert into articles(title,url,url_object_id,font_img_url,font_img_path,create_time,fa_num,sc_num,pinglun_num,tag,content)
               VALUES(%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)
               """
        cursor.execute(inser_sql, (item["title"], item["url"], item["url_object_id"],
                                   item["front_image_url"], item["front_image_path"], item["create_date"],
                                   item["praise_nums"], item["fav_nums"], item["comment_nums"], item["tags"],
                                   item["content"]))
技术分享图片

 

   4.item_loader的使用(jobbole.py):代码更加简洁,但值全为list,定制需要修改item中函数

技术分享图片
from scrapy.loader import ItemLoader
...... 
def parse_detail(self,response):
 #通过item_loader加载item
        item_loader=ItemLoader(item=JobboleArticleSpider(),response=response)
        #三个重要方法item_loader.add_xpath();item_loader.add_css();item_loader.add_css()
        item_loader.add_css("title",".entry-header > h1::text")
        item_loader.add_value("url",response.url)
        item_loader.add_value("url_ooject_id",get_md5(front_image_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","span.vote-post-up h10::text")
        item_loader.add_css("fav_nums",".bookmark-btn::text")
        item_loader.add_css("comments_nums", "a[href=‘#article-comment‘] span::text")
        item_loader.add_css("content", "div.entry")
        item_loader.add_css("tags", "p.entry-meta-hide-on-mobile a::text")
     #调用此方法才生效
        article_item=item_loader.load_item()
        yield article_item
技术分享图片

 

   5.修改item处理得到的函数:

技术分享图片
from scrapy.loader.processors import MapCompose,TakeFirst
......

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


class JobboleArticleSpider(scrapy.Item):
    # 字段中有Field类型,可以接受任何类型
    title = scrapy.Field(
    #可以传多个函数 input_processor
=MapCompose(lambda x: x + "hah") ) create_date = scrapy.Field( #处理时间,还是数组 input_processor=MapCompose(date_convert), #只取数组的第一个,如果都要写麻烦,可以定制itemloader output_processor=TakeFirst() ) url = scrapy.Field() # 对url做MD5,固定url的长度 url_object_id = scrapy.Field() front_image_url = scrapy.Field() front_image_path = scrapy.Field() praise_nums = scrapy.Field() fav_nums = scrapy.Field() comment_nums = scrapy.Field() tags = scrapy.Field() content = scrapy.Field()
技术分享图片

 

 

   6.定制itemloader:

items,py:

from scrapy.loader import ItemLoader
......
class ArticleItemLoader(ItemLoader):
    #自定义itemloader,值取数组的第一个,修改item中的loader
    default_output_processor = TakeFirst()

 

 jobbole.py:

技术分享图片
......
 item_loader=ArticleItemLoader(item=JobboleArticleSpider(),response=response)
        #三个重要方法item_loader.add_xpath();item_loader.add_css();item_loader.add_css()
        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(front_image_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","span.vote-post-up h10::text")
        item_loader.add_css("fav_nums",".bookmark-btn::text")
        item_loader.add_css("comment_nums", "a[href=‘#article-comment‘] span::text")
        item_loader.add_css("content", "div.entry")
        item_loader.add_css("tags", "p.entry-meta-hide-on-mobile a::text")

        article_item=item_loader.load_item()
        yield article_item
技术分享图片

 技术分享图片

用itemloader方法定制的item(items.py):

技术分享图片
# -*- coding: utf-8 -*-

# Define here the models for your scraped items
#
# See documentation in:
# https://doc.scrapy.org/en/latest/topics/items.html
import datetime
import re

import scrapy
# TakeFirst取第一个,Join连接
from scrapy.loader.processors import MapCompose, TakeFirst, Join
from scrapy.loader import ItemLoader


class ArticleSpiderItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    pass


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


class ArticleItemLoader(ItemLoader):
    # 自定义itemloader,值取数组的第一个,修改item中的loader
    default_output_processor = TakeFirst()


def get_nums(value):
    # 定义处理点赞数,收藏数,评论数处理等
    match_num = re.match(".*(\d+).*", value)
    if match_num:
        value = int(match_num.group(1))
    else:
        value = 0
    return value


def return_value(value):
    # 什么也不做
    return value


def remove_comment(value):
    # 去掉tag中提取的含评论的便签
    if "评论" in value:
        return ""
    else:
        return value


class JobboleArticleSpider(scrapy.Item):
    # 字段中有Field类型,可以接受任何类型
    title = scrapy.Field()
    create_date = scrapy.Field(
        # 处理时间,还是数组
        input_processor=MapCompose(date_convert),
        # 只取数组的第一个
        # output_processor=TakeFirst()
    )
    url = scrapy.Field()
    # 对url做MD5,固定url的长度
    url_object_id = scrapy.Field()
    front_image_url = scrapy.Field(
  #覆盖定制的itemloader,这里必须为列表 outout_processor
=MapCompose(return_value) ) front_image_path = scrapy.Field() praise_nums = scrapy.Field( input_processor=MapCompose(get_nums) ) fav_nums = scrapy.Field( input_processor=MapCompose(get_nums) ) comment_nums = scrapy.Field( input_processor=MapCompose(get_nums) ) tags = scrapy.Field( # 覆盖定制的取第一个 output_processor=Join(",") ) content = scrapy.Field()
技术分享图片

 

四.总结

  利用上面的方法就可以快速爬取所有文章了,scrapy是一个分布式的设计,也是多线程,写爬虫的主要部分就是在spider中定制爬虫要爬取的url及填充数据(jobbole.py),以及定制item的模板(items.py),然后就是定制pipeline对item中的数据进行一系列操作,如写入json文件,导入数据库,下载图片,获取图片路径等等。

      

 

 

 

  

           

 

                

        

 

目标任务:爬取腾讯社招信息,需要爬取的内容为:职位名称,职位的详情链接,职位类别,招聘人数,工作地点,发布时间。

一、创建Scrapy项目

scrapy startproject Tencent

命令执行后,会创建一个Tencent文件夹,结构如下

二、编写item文件,根据需要爬取的内容定义爬取字段

技术分享图片
# -*- coding: utf-8 -*-

import scrapy

class TencentItem(scrapy.Item):

    # 职位名
    positionname = scrapy.Field()
    # 详情连接
    positionlink = scrapy.Field()
    # 职位类别
    positionType = scrapy.Field()
    # 招聘人数
    peopleNum = scrapy.Field()
    # 工作地点
    workLocation = scrapy.Field()
    # 发布时间
    publishTime = scrapy.Field()
技术分享图片

三、编写spider文件

进入Tencent目录,使用命令创建一个基础爬虫类:

#  tencentPostion为爬虫名,tencent.com为爬虫作用范围
scrapy genspider tencentPostion "tencent.com"

执行命令后会在spiders文件夹中创建一个tencentPostion.py的文件,现在开始对其编写:

技术分享图片
# -*- coding: utf-8 -*-
import scrapy
from tencent.items import TencentItem

class TencentpositionSpider(scrapy.Spider):
    """
    功能:爬取腾讯社招信息
    """
    # 爬虫名
name
= "tencentPosition"
# 爬虫作用范围
allowed_domains = ["tencent.com"] url = "http://hr.tencent.com/position.php?&start=" offset = 0 # 起始url start_urls = [url + str(offset)] def parse(self, response): for each in response.xpath("//tr[@class=‘even‘] | //tr[@class=‘odd‘]"): # 初始化模型对象 item = TencentItem() # 职位名称 item[positionname] = each.xpath("./td[1]/a/text()").extract()[0] # 详情连接 item[positionlink] = each.xpath("./td[1]/a/@href").extract()[0] # 职位类别 item[positionType] = each.xpath("./td[2]/text()").extract()[0] # 招聘人数 item[peopleNum] = each.xpath("./td[3]/text()").extract()[0] # 工作地点 item[workLocation] = each.xpath("./td[4]/text()").extract()[0] # 发布时间 item[publishTime] = each.xpath("./td[5]/text()").extract()[0] yield item if self.offset < 1680: self.offset += 10 # 每次处理完一页的数据之后,重新发送下一页页面请求 # self.offset自增10,同时拼接为新的url,并调用回调函数self.parse处理Response yield scrapy.Request(self.url + str(self.offset), callback = self.parse)
技术分享图片

四、编写pipelines文件

技术分享图片
# -*- coding: utf-8 -*-
import json

class TencentPipeline(object):
  """
功能:保存item数据
"""
def __init__(self): self.filename = open("tencent.json", "w") def process_item(self, item, spider): text = json.dumps(dict(item), ensure_ascii = False) + ",\n" self.filename.write(text.encode("utf-8")) return item def close_spider(self, spider): self.filename.close()
技术分享图片

五、settings文件设置(主要设置内容)

技术分享图片
# 设置请求头部,添加url
DEFAULT_REQUEST_HEADERS = {
    "User-Agent" : "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0;",
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
}

# 设置item——pipelines
ITEM_PIPELINES = {
    tencent.pipelines.TencentPipeline: 300,
}
技术分享图片

执行命令,运行程序

# tencentPosition为爬虫名
scrapy crwal tencentPosition

 

 

使用CrawlSpider类改写

# 创建项目
scrapy startproject TencentSpider

# 进入项目目录下,创建爬虫文件
scrapy genspider -t crawl tencent tencent.com

item等文件写法不变,主要是爬虫文件的编写

技术分享图片
# -*- coding:utf-8 -*-

import scrapy
# 导入CrawlSpider类和Rule
from scrapy.spiders import CrawlSpider, Rule
# 导入链接规则匹配类,用来提取符合规则的连接
from scrapy.linkextractors import LinkExtractor
from TencentSpider.items import TencentItem

class TencentSpider(CrawlSpider):
    name = "tencent"
    allow_domains = ["hr.tencent.com"]
    start_urls = ["http://hr.tencent.com/position.php?&start=0#a"]

    # Response里链接的提取规则,返回的符合匹配规则的链接匹配对象的列表
    pagelink = LinkExtractor(allow=("start=\d+"))

    rules = [
        # 获取这个列表里的链接,依次发送请求,并且继续跟进,调用指定回调函数处理
        Rule(pagelink, callback = "parseTencent", follow = True)
    ]

    # 指定的回调函数
    def parseTencent(self, response):
        for each in response.xpath("//tr[@class=‘even‘] | //tr[@class=‘odd‘]"):
            item = TencentItem()
            # 职位名称
            item[positionname] = each.xpath("./td[1]/a/text()").extract()[0]
            # 详情连接
            item[positionlink] = each.xpath("./td[1]/a/@href").extract()[0]
            # 职位类别
            item[positionType] = each.xpath("./td[2]/text()").extract()[0]
            # 招聘人数
            item[peopleNum] =  each.xpath("./td[3]/text()").extract()[0]
            # 工作地点
            item[workLocation] = each.xpath("./td[4]/text()").extract()[0]
            # 发布时间
            item[publishTime] = each.xpath("./td[5]/text()").extract()[0]

            yield item
技术分享图片

 

python爬虫scrapy框架及例子

原文:https://www.cnblogs.com/abdm-989/p/12129799.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!