Python-scrapy爬虫

Python-scrapy爬虫

目录

  • Python-scrapy爬虫
    • CHAPTER2
      • 1.HTTP基本原理
      • 2.HTML
        • HTML
        • JavaScript
        • CSS
      • 3.使用XPath定位
    • CHAPTER3
      • Scrapy框架
      • 安装scrapy
      • 项目:起点中文网小说数据
    • CHAPTER4
      • request对象
        • 应用:伪装成浏览器
      • 使用选择器提取数据
      • Response对象
        • 使用CSS定位
      • Item封装数据
      • ItemLoader填充数据
      • Pipeline中处理数据
        • 获取下一页url,并生成一个Request请求
      • 项目:链家网二手房信息
        • 创建项目
        • 使用Item封装数据
        • 创建Spider源文件及Spider类
        • 使用Pipeline实现数据的处理
        • 其他设置
        • 在pycharm中运行爬虫
    • CHAPTER5 数据库
      • MySQL数据库
        • 安装MySQL并开启服务
        • 数据库管理工具-Navicat
        • Navicat连接MySQL数据库
        • Navicat新建数据库和表
      • 操作MySQL数据库
        • 1.连接MySQL数据库服务器
        • 2.获取操作游标
        • 3.执行SQL语句
        • 4.回滚
        • 5.提交数据
        • 6.关闭游标及数据库
      • 项目:qidian_hot爬取到的小说信息存储于MySQL中
      • MongoDB数据库
        • NoSQL概述
        • Python访问MongoDB数据库
          • 1.连接MongoDB数据库服务器
          • 2.指定数据库
          • 3.指定集合(相当于关系型数据库中的表)。
          • 4.插入文档
          • 5.查询文档
          • 6.更新文档
          • 7.删除文档
          • 8.关闭数据库。
      • Redis数据库
        • 配置Redis数据库
        • Python访问Redis数据库
        • Redis操作
          • 1.连接Redis数据库服务器
          • 2.字符串(String)操作
          • 3.列表(List)操作
          • 4.无序集合(Set)操作
          • 5.散列表(Hash)操作
          • 6.有序集合(Storted Set)操作
    • CHAPTER6 JavaScript和Ajax数据爬取
      • JavaScript简介
        • 项目:QQ音乐榜单
          • 1.创建项目
          • 2.使用Item封装数据
          • 3.创建Spider文件及Spider类
          • 4.运行爬虫
          • 5.查看结果
      • Ajax简介
        • 项目:豆瓣电影
          • 1.创建项目
          • 2.使用Item封装数据
          • 3.创建Spider文件及Spider类
          • 4.运行爬虫
          • 5.查看结果
    • CHAPTER7 动态渲染页面爬取
      • Selenium实现动态页面爬取
        • 环境搭建:Scrapy+Selenium+PhantomJS
          • 安装selenium
          • 安装chromedriver
          • 安装phantomJS
        • Selenium语法
      • 项目:今日头条
          • 1.创建项目
          • 2.使用Item封装数据
          • 3.使用DownloaderMiddleware处理request
          • 4.创建Spider文件及Spider类
          • 4.运行爬虫
          • 5.查看结果

CHAPTER2

1.HTTP基本原理

  • 全称Uniform Resource Locator,即统一资源定位符。

    如:https://www.baidu.com/包含了:

    • 访问协议: HTTPS,用于确定数据传输的方式。

    • 服务器名称:www.baidu.com,网站地址。

  • HTTP协议

    全称HyperText Transfer Protocol,即超文本传输协议。HTTP协议以明文方式发送内容,不提供任何方式的数据加密。

  • HTTPS协议

    全称HyperText Transfer Protocol over Secure Socket Layer,即安全套接字层超文本传输协议。HTTPS在HTTP的基础上加入了SSL协议,SSL依靠证书来验证服务器的身份,并对浏览器和服务器之间的通信加密。

  • HTTP请求过程

    • 请求方法:常见的请求方法有两种:GET和POST

    • 请求的网址:有Host,Cookie,User-Agent等

    • 请求头

      请求头 说明
      Accept 浏览器端可以接受的媒体类型。
      Accept-Encoding 浏览器接受的编码方式。
      Accept-Language 浏览器所接受的语言种类。
      Connection 表示是否需要持久连接。
      Cookie 网站为了辨别用户身份、进行会话跟踪而储存在用户本地的数据(通常经过加密),由网站服务器创建。
      Host 指定被请求资源的Internet主机和端口号,通常从URL中提取出来。
      User-Agent 告诉网站服务器,客户端使用的操作系统和浏览器的名称和版本、CPU版本、浏览器渲染引擎、浏览器语言等。
    • 请求体:一般承载的内容是POST请求中的表单数据,而对于GET请求,请求体则为空

  • HTTP响应过程

    • 响应状态码(Response Status Code): 如200代表服务器正常响应,404代表页面未找到

    • 响应头(Response Headers):包含了服务器对请求的应答信息,如Content-Type、Server、Set-Cookie等

    • 响应体(Response Body):包含响应的正文数据,如网页的HTML代码。

2.HTML

HTML

用来描述网页的一种语言,全称叫做Hyper Text Markup Language,即超文本标记语言。网页包括文字、按钮、图片和视频等各种复杂的元素,其基础架构就是HTML。

例:movies.html

电影排行

电影排行榜单

1.肖申克的救赎

2.霸王别姬

JavaScript

简称JS,是一种脚本语言。HTML和CSS配合使用,提供给用户的只是一种静态信息,缺乏交互性。我们在网页里可能会看到一些交互和动画效果,如下载进度条、提示框、轮播图等,这通常就是JavaScript的功劳。

CSS

HTML定义了网页的结构,但是只有HTML页面的布局并不美观,可能只是简单的节点元素的排列,为了让网页看起来更好看一些,需要CSS的帮助。

全称叫作Cascading Style Sheets,即层叠样式表。“层叠”是指当在HTML中引用了数个样式文件,并且样式发生冲突时,浏览器能依据层叠顺序处理。

3.使用XPath定位

# 使用Xpath提取网页

# 导入lxml库的etree模块
from lxml import etree
# 解析movies.html文件,返回一个节点树的对象
html_selector = etree.parse("movies.html",etree.HTMLParser())

root = html_selector.xpath("/html/head/title/text()")
print(root[0])

movies = html_selector.xpath("//p/text()")
print(movies)

XPath全称XML Path Language,即XML路径语言。它是一门在XML文档中查找信息的语言。HTML与XML结构类似,也可以在HTML中查找信息。

安装XPath

>pip install lxml

表达式 描述 示例
nodename 选取此节点的所有子节点 div,p,h1
/ 从根节点选取(描述绝对路径) /html
// 不考虑位置,选取页面中所有子孙节点 //div
. 选取当前节点(描述相对路径) ./div
选取当前节点的父节点(描述相对路径) h1/…/
**@**属性名 选取属性的值 @href,@id
text() 获取元素中的文本节点 //h1/text()
谓语表达式 说明 结果
//div[@id=‘content’] 选取属性id为content的div元素 []
//div[@class] 选取所有带有属性class的div元素 []
//div/p[1]/text() 选取div节点中的第一个p元素的文本 [‘1.肖申克的救赎’]
//div/p[2]/text() 选取div节点中的第二个p元素的文本 [‘2.霸王别姬’]
//div/p[last()]/text() 选取div节点中的最后一个p元素的文本 [‘2.霸王别姬’]

CHAPTER3

Scrapy框架

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fOZkOZCY-1601619554324)(/Users/jack/Library/Application Support/typora-user-images/image-20200814234020395.png)]

引擎(ENGINE)

调度器(SCHEDULER)

下载器(DOWNLOADER)

爬虫(SPIDERS)

项目管道(ITEM PIPELINES)

下载器中间件(Downloader Middlewares)

爬虫中间件(Spider Middlewares)

第①步:爬虫(Spider)使用URL(要爬取页面的网址)构造一个请求(Request)对象,提交给引擎(ENGINE)。如果请求要伪装成浏览器,或者设置代理IP,可以先在爬虫中间件中设置,再发送给引擎。

第②步:引擎将请求安排给调度器,调度器根据请求的优先级确定执行顺序。

第③步:引擎从调度器获取即将要执行的请求。

第④步:引擎通过下载器中间件,将请求发送给下载器下载页面。

第⑤步:页面完成下载后,下载器会生成一个响应(Response)对象并将其发送给引擎。下载后的数据会保存于响应对象中。

第⑥步:引擎接收来自下载器的响应对象后,通过爬虫中间件,将其发送给爬虫(Spider)进行处理。

第⑦步:爬虫将抽取到的一条数据实体(Item)和新的请求(如下一页的链接)发送给引擎。

第⑧步:引擎将从爬虫获取到的Item发送给项目管道(ITEM PIPELINES),项目管道实现数据持久化等功能。同时将新的请求发送给调度器,再从第②步开始重复执行,直到调度器中没有更多的请求,引擎关闭该网站。

安装scrapy

>pip install scrapy

项目:起点中文网小说数据

创建项目

# 终端
cd 项目文件夹
scrapy startproject 项目名

pycharm打开项目文件夹,在spiders文件夹中新建爬虫文件,如qidian_hot_spider.py

# -*-coding:utf-8-*-

from scrapy import Request
from scrapy.spiders import Spider

class HotSalesSpider(Spider):
    name = "hot" # 爬虫名称
    start_urls = ["https://www.qidian.com/rank/hotsales?style=1&page=1"]
def parse(self,response): # 数据解析
    # 使用xpath定位
    list_selector = response.xpath("//div[@class='book-mid-info']")
    for one_selector in list_selector:
        # 获取小说信息
        name = one_selector.xpath("h4/a/text()").extract()[0]
        # 获取作者
        author = one_selector.xpath("p[1]/a[1]/text()").extract()[0]
        # 获取类型
        type = one_selector.xpath("p[1]/a[2]/text()").extract()[0]
        # 获取形式
        form = one_selector.xpath("p[1]/span/text()").extract()[0]
        # 定义字典
        hot_dict = {
     
            "name":name,
            "author":author,
            "type":type,
            "form":form
        }
        yield hot_dict #使用yield返回字典

终端中运行,

# 终端
# scrapy crawl 爬虫名称 -o 输出文件
scrapy crawl hot -o hot.csv

CHAPTER4

request对象

Request用来描述一个HTTP请求,它通常在Spider中生成并由下载器执行。

Request的定义形式为:

class scrapy.http.Request(url[,callback,method=‘GET’,headers,body,cookies,meta,encoding=‘utf-8’,priority=0,dont_filter=False,errback])

参数 说明
url HTTP请求的网址
method HTTP请求的方法,如“GET”、“POST”、“PUT”等,默认为“GET”,必须大写。
body HTTP的请求体,类型为str或unicode。
headers HTTP的请求头,字典型。
cookies 请求的Cookie值,字典型或列表型,可以实现自动登录的效果。
encoding 请求的编码方式,默认为UTF-8。
callback 指定回调函数,即确定页面解析函数,默认为parse()。
meta 字典类型,用于数据的传递。
priority 请求的优先级,默认为0,优先级高的请求优先下载。
dont_filter 如果对同一个url多次提交相同请求,可以使用此项来忽略重复的请求,避免重复下载,默认为False。
errback 在处理请求时引发任何异常时调用的函数。

应用:伪装成浏览器

重写start_requests()方法

重写(override)start_requests()方法,手动生成一个功能更强大的Request对象。因为伪装浏览器、自动登录等功能都是在Request对象中设置的。

引擎之所以能自动定位,是因为在Request对象中,指定了解析数据的回调函数(callback),而默认情况下,Request指定的解析函数就是parse()方法,即callback=self.parse

# -*-coding:utf-8-*-

from scrapy import Request
from scrapy.spiders import Spider

class HotSalesSpider(Spider):
    name = "hot" # 爬虫名称
    qiandian_headers = {
     "User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.89 Safari/537.36"} # 复制浏览器中request headers中的User-Agent,伪装成浏览器

    def start_requests(self): # 重写start_requests()方法
        url = "https://www.qidian.com/rank/hotsales?style=1&page=1"
        yield Request(url,headers=self.qiandian_headers)

    def parse(self,response): # 数据解析
        # 使用xpath定位
        list_selector = response.xpath("//div[@class='book-mid-info']")
        for one_selector in list_selector:
            # 获取小说信息
            name = one_selector.xpath("h4/a/text()").extract()[0]
            # 获取作者
            author = one_selector.xpath("p[1]/a[1]/text()").extract()[0]
            # 获取类型
            type = one_selector.xpath("p[1]/a[2]/text()").extract()[0]
            # 获取形式
            form = one_selector.xpath("p[1]/span/text()").extract()[0]
            # 定义字典
            hot_dict = {
     
                "name":name,
                "author":author,
                "type":type,
                "form":form
            }
            yield hot_dict

使用选择器提取数据

Scrapy提取数据有自己的一套机制,被称作选择器(Selector类),它能够自由“选择”由XPath或CSS表达式指定的HTML文档的某些部分。Scrapy的选择器短小简洁、解析快、准确性高,使用其内置的方法可以快速地定位和提取数据。

定位数据:

  • xpath(query)

  • css(query)

提取数据:

  • extract()

  • extract_first()(SelectorList独有的方法)

  • re(regex)

  • re_first(SelectorList独有的方法)

Response对象

Response用来描述一个HTTP响应,它只是一个基类。当下载器下载完网页后,下载器根据HTTP响应头部的Content-Type自动创建Response的子类对象。子类主要有:

  • TextResponse

  • HtmlResponse

  • XmlResponse

参数 说明
url 响应的url,只读
status HTTP响应的状态码,如200,403,404。
body HTTP响应体。
headers HTTP的响应头,字典型。
meta 用于接收传递的数据。

response接收封装有网页信息的Response对象,这时就可以使用下面的方法实现对数据的定位。

response.selector.xpath(query)

response.selector.css(query)

由于在response中使用XPath、CSS查询十分普遍,因此Response对象提供了两个实用的快捷方式,它们能自动创建选择器并调用选择器的xpath或css方法来定位数据。简化后的方法如下所示:

response.xpath(query)

response.css(query)

使用CSS定位

CSS全称Cascading Style Sheets,即层叠样式表,用于表现HTML或XML的样式。CSS表达式的语法比XPath简洁,但是功能不如XPath强大,大多作为XPath的辅助。

表达式 描述 示例
***** 选取所有元素 *
E 选取E元素 div
E1,E2 选取E1和E2元素 div,p
E1>E2 选取E1的子元素E2 div>h1
E1 E2 选取E1子孙中的E2元素 div h1
.class 选取CLASS属性的值为class的元素 .author
#id 选取ID属性的值为id的元素 #name
[ATTR] 选取包含ATTR属性的元素 [href]
[ATTR=VALUE] 选取属性ATTR的值为VALUE的元素 [class=author]
E:nth-child(n) 选取E元素且该元素是其父元素的第n个子元素 p:nth-child(1)
E:nth-last-child(n) 选取E元素且该元素是其父元素的倒数第n个子元素 p:nth-last-child(1)
E::text 获取E元素的文本 h1::text
# -*-coding:utf-8-*-

from scrapy import Request
from scrapy.spiders import Spider

class HotSalesSpider(Spider):
    name = "hot" # 爬虫名称
    qiandian_headers = {
     "User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.89 Safari/537.36"}

    def start_requests(self):
        url = "https://www.qidian.com/rank/hotsales?style=1&page=1"
        yield Request(url,headers=self.qiandian_headers,callback=self.css_parse)

    def css_parse(self,response): # 数据解析
        # 使用css定位
        list_selector = response.css("[class='book-mid-info']")
        for one_selector in list_selector:
            # 获取小说信息
            name = one_selector.css("h4>a::text").extract()[0]
            # 获取作者
            author = one_selector.css(".author a::text").extract()[0]
            # 获取类型
            type = one_selector.css(".author a::text").extract()[1]
            # 获取形式
            form = one_selector.css(".author span::text").extract()[0]
            # 定义字典
            hot_dict = {
     
                "name":name,
                "author":author,
                "type":type,
                "form":form
            }
            yield hot_dict

Item封装数据

Item对象是一个简单的容器,用于收集抓取到的数据,其提供了类似于字典(dictionary-like)的API,并具有用于声明可用字段的简单语法。

items.py

import scrapy
#保存小说热销榜字段数据
 class QidianHotItem(scrapy.Item):
   # define the fields for your item here like:
   name = scrapy.Field() #小说名
   author = scrapy.Field()#作者
   type = scrapy.Field() #类型
   form = scrapy.Field() #形式

1.类QidianHotItem继承于Scrapy的Item类。

2.name、author、type和form为小说的各个字段名。

3.scrapy.Field()生成一个Field对象,赋给各自的字段。

4.Field对象用于指定每个字段的元数据,并且Field对象对接受的数据没有任何限制。因此,在定义属性字段时,无需考虑它的数据类型,使用起来非常方便。

# -*-coding:utf-8-*-

from scrapy import Request
from scrapy.spiders import Spider
from qidian_hot.items import QidianHotItem

class HotSalesSpider(Spider):
    name = "hot" # 爬虫名称
    qiandian_headers = {
     "User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.89 Safari/537.36"}

    def start_requests(self):
        url = "https://www.qidian.com/rank/hotsales?style=1&page=1"
        yield Request(url,headers=self.qiandian_headers,callback=self.parse)

    def parse(self,response): # 数据解析
        # 使用xpath定位
        list_selector = response.xpath("//div[@class='book-mid-info']")
        for one_selector in list_selector:
            # 获取小说信息
            name = one_selector.xpath("h4/a/text()").extract()[0]
            # 获取作者
            author = one_selector.xpath("p[1]/a[1]/text()").extract()[0]
            # 获取类型
            type = one_selector.xpath("p[1]/a[2]/text()").extract()[0]
            # 获取形式
            form = one_selector.xpath("p[1]/span/text()").extract()[0]
            # ITEM封装数据
            item = QidianHotItem()
            item["name"] = name
            item["author"] = author
            item["type"] = type
            item["form"] = form
            yield item

ItemLoader填充数据

当项目很大,提取的字段数以百计时,数据的提取规则也会越来越多,再加上还要对提取到的数据做转换处理,代码就会变得庞大,维护起来十分困难。

为了解决这个问题,Scrapy提供了项目加载器(ItemLoader)这样一个填充容器。通过填充容器,可以配置Item中各个字段的提取规则,并通过函数分析原始数据,最后对Item字段赋值,使用起来非常便捷。

Item和ItemLoader的区别:

1.Item提供保存抓取到数据的**容器,**需要手动将数据保存于容器中。

2.Itemloader提供的是填充容器的机制。

# -*-coding:utf-8-*-

from scrapy import Request
from scrapy.spiders import Spider
from qidian_hot.items import QidianHotItem
from scrapy.loader import ItemLoader

class HotSalesSpider(Spider):
    name = "hot" # 爬虫名称
    qiandian_headers = {
     "User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.89 Safari/537.36"}

    def start_requests(self):
        url = "https://www.qidian.com/rank/hotsales?style=1&page=1"
        yield Request(url,headers=self.qiandian_headers,callback=self.parse)

    def parse(self,response): # 数据解析
        # 使用xpath定位
        list_selector = response.xpath("//div[@class='book-mid-info']")
        for one_selector in list_selector:
            # 生成ItemLoader实例
            novel = ItemLoader(item = QidianHotItem(),selector = one_selector)
            novel.add_xpath("name","h4/a/text()")
            novel.add_xpath("author","p[1]/a[1]/text()")
            novel.add_xpath("type","p[1]/a[2]/text()")
            novel.add_xpath("form","p[1]/span/text()")

            yield novel.load_item()

Pipeline中处理数据

当Spider将收集到的数据封装为Item后,它将会被传递到Item Pipeline(项目管道)组件中等待进一步处理。

在Scrapy中,Item Pipeline是可选组件,默认关闭,要想激活它,只需在配置文件settings.py中启用被注释掉的代码即可。

# settings.py
ITEM_PIPELINES = {
     
   'qidian_hot.pipelines.QidianHotPipeline': 300,
}

300是pipeline的优先级,对于多个pipeline,数字越小的优先处理

# pipelines.py
from scrapy.exceptions import DropItem

class QidianHotPipeline:
    def __init__(self):
        self.author_set = set()


    def process_item(self, item, spider): # 接收spider中的item
        if item["name"] in self.author_set:
            raise DropItem("查找到重复姓名的项目:%s"%item)
        return item

获取下一页url,并生成一个Request请求

# -*-coding:utf-8-*-

from scrapy import Request
from scrapy.spiders import Spider
from qidian_hot.items import QidianHotItem

class HotSalesSpider(Spider):
    name = "hot" # 爬虫名称
    qiandian_headers = {
     "User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.89 Safari/537.36"}
    current_page = 1

    def start_requests(self):
        url = "https://www.qidian.com/rank/hotsales?style=1&page=1"
        yield Request(url,headers=self.qiandian_headers,callback=self.parse)

    def parse(self,response): # 数据解析
        # 使用xpath定位
        list_selector = response.xpath("//div[@class='book-mid-info']")
        for one_selector in list_selector:
            # 获取小说信息
            name = one_selector.xpath("h4/a/text()").extract()[0]
            # 获取作者
            author = one_selector.xpath("p[1]/a[1]/text()").extract()[0]
            # 获取类型
            type = one_selector.xpath("p[1]/a[2]/text()").extract()[0]
            # 获取形式
            form = one_selector.xpath("p[1]/span/text()").extract()[0]
            # ITEM封装数据
            item = QidianHotItem()
            item["name"] = name
            item["author"] = author
            item["type"] = type
            item["form"] = form
            yield item

            # 获取下一页url,并生成一个Request请求
            self.current_page += 1
            if self.current_page <= 25:
                next_url = "https://www.qidian.com/rank/hotsales?style=1&page=%d"%self.current_page
                yield Request(next_url,callback=self.parse)

项目:链家网二手房信息

要求:https://su.lianjia.com/ershoufang/

1.房屋面积、总价和单价只需要具体的数字,不需要单位名称。

2.删除字段不全的房屋数据,如有的房屋朝向会显示“暂无数据”,应该剔除。

3.保存到CSV文件中的数据,字段要按照如下顺序排列:房屋名称,房屋户型,建筑面积,房屋朝向,装修情况,有无电梯,房屋总价,房屋单价,房屋产权。

创建项目

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AEuiAtrL-1601619554327)(/Users/jack/Library/Application Support/typora-user-images/image-20200815200956787.png)]

使用Item封装数据

items.py

import scrapy

class LianjiaItem(scrapy.Item):
    # define the fields for your item here like:
    name = scrapy.Field() # 房屋名称
    type = scrapy.Field() # 户型
    area = scrapy.Field() # 面积
    direction = scrapy.Field() # 朝向
    fitment = scrapy.Field() # 装修情况
    elevator = scrapy.Field() # 电梯
    total_price = scrapy.Field() # 总价
    unit_price = scrapy.Field() # 单价
    property = scrapy.Field() # 产权
    pass

创建Spider源文件及Spider类

lianjia_spider.py

# -*- coding: utf-8 -*-
from scrapy import Request
from scrapy.spiders import Spider
from lianjia.items import LianjiaItem

class HomeSpider(Spider):
    name = "home"
    current_page = 1
    total_page = 0 #总页数

    def start_requests(self): # 获取初始请求(start_requests())
        url = "https://su.lianjia.com/ershoufang/"
        yield Request(url)

    def parse(self, response): #实现主页面解析函数(parse())
        list_selector = response.xpath("//ul/li/div[@class='info clear']")
        for one_selector in list_selector:
            try:
                # 房屋名称
                name = one_selector.xpath("div[@class='flood']/div[@class='positionInfo']/a[1]/text()").extract_first()
                # 其他信息
                other = one_selector.xpath("div[@class='address']/div[@class='houseInfo']/text()").extract_first()
                other_list = other.split("|")
                type = other_list[0].strip(" ")
                area = other_list[1].strip(" ")
                direction = other_list[2].strip(" ")
                fitment = other_list[3].strip(" ")
                # 价格
                price_list = one_selector.xpath("div[@class='priceInfo']//span/text()")
                total_price = price_list[0].extract()
                unit_price = price_list[1].extract()

                item = LianjiaItem()
                item["name"] = name.strip(" ")
                item["type"] = type
                item["area"] = area
                item["direction"] = direction
                item["fitment"] = fitment
                item["total_price"] = total_price
                item["unit_price"] = unit_price

                # 生成详细页的url
                url = one_selector.xpath("div[@class='title']/a/@href").extract_first()
                yield Request(url,
                              meta = {
     "item":item},
                              callback = self.property_parse)
            except:
                pass
        # 获取下一页url
        if self.current_page == 1:
            #属性page-data的值中包含总页数和当前页。
            self.total_page = response.xpath("//div[@class='page-box house-lst-page-box']//@page-data").re("\d+")
            self.total_page = int(self.total_page[0]) #获取总页数

        self.current_page+=1 #下一页的值
        if self.current_page <= self.total_page: #self.total_page:#判断页数是否已越界
            next_url = "https://su.lianjia.com/ershoufang/pg%d"%(self.current_page)

            yield Request(next_url) #根据URL生成Request,使用yield提交给引擎

    def property_parse(self, response): #实现详细页解析函数
        property = response.xpath("//div[@class='introContent']/div[@class='transaction']/div[@class='content']/ul/li[2]/span[2]/text()").extract_first()
        elevator = response.xpath("//div[@class='introContent']/div[@class='base']/div[@class='content']/ul/li[last()]/text()").extract_first()

        item = response.meta["item"]
        item["property"] = property
        item["elevator"] = elevator
        yield item

使用Pipeline实现数据的处理

Pipelines.py

# -*- coding: utf-8 -*-
import re # 正则表达式模块
from scrapy.exceptions import DropItem

class LianjiaPipeline(object):
    def process_item(self, item, spider):
        # 提取数字
        item["area"] = re.findall("\d+\.?\d*",item["area"])[0]
        item["unit_price"] = re.findall("\d+\.?\d*",item["unit_price"])[0]
        # 剔除数据缺失项
        if item["elevator"] == "暂无数据":
            raise DropItem("无数据,抛弃此项目:%s"%item)
        return item # process_item一定要有return

class CSVPipeline(object):
    file = None # 文件对象
    index = 0
    def open_spider(self,spider): # 爬虫打开时执行打开文件操作
        self.file = open("home11.csv","a",encoding="utf-8") #以追加形式打开文件

    def process_item(self, item, spider):
        if self.index == 0:
            column_name = "name,type,area,direction,fitment,elevator,total_price,unit_price,property\n"
            self.file.write(column_name)
            self.index=1
        home_str = item["name"]+","+\
                   item["type"]+","+\
                   item["area"]+","+\
                   item["direction"]+","+\
                   item["fitment"]+","+ \
                   item["elevator"] + "," + \
                   item["total_price"] + "," + \
                   item["unit_price"] + "," + \
                   item["property"]+"\n"
        self.file.write(home_str) #字符串写入文件中
        return item

    def close_spider(self,spider): # 爬虫结束时执行关闭文件
        self.file.close()

其他设置

Settings.py

# 伪装浏览器访问
# Crawl responsibly by identifying yourself (and your website) on the user-agent
USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.89 Safari/537.36'

# 不遵守网站的robots规则
# Obey robots.txt rules
ROBOTSTXT_OBEY = False

# 启用两个pipelines
# Configure item pipelines
ITEM_PIPELINES = {
     
   'lianjia.pipelines.LianjiaPipeline': 300,
    'lianjia.pipelines.CSVPipeline': 400,
}

在pycharm中运行爬虫

运行start.py

from scrapy import cmdline
cmdline.execute("scrapy crawl home".split())

CHAPTER5 数据库

MySQL数据库

关系型数据库,是建立在关系模型基础上的数据库。简单讲,它由多张能互相联结的二维表格组成,每一行是一条记录,每一列是一个字段,而表就是某个实体的集合,它展现的形式类似于EXCEL中常见的表格。

像SQLite、MySQL、Oracle、SQL Server、DB2等都属于关系型数据库。

安装MySQL并开启服务

https://dev.mysql.com/downloads/

数据库管理工具-Navicat

Navicat是一个强大的数据库管理和设计工具。通过直观的GUI(图形用户界面),让用户简单地管理MySQL、MongoDB、SQL Server、Oracle等数据库。

Navicat连接MySQL数据库

Navicat新建数据库和表

字符集:UTF-8

排序规则:UTF8-unicode-ci

Python访问MySQL数据库:pip install mysqlclient

操作MySQL数据库

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uqRyvNsD-1601619554328)(/Users/jack/Library/Application Support/typora-user-images/image-20200821000843506.png)]

1.连接MySQL数据库服务器

调用方法MySQLdb.connect(db,host,user,password,charset)。对应的参数有:

db:数据库名。

host:主机。

user:用户名。

password:密码。

charset:编码格式。

2.获取操作游标

调用Connection对象的cursor()方法,获取操作游标,实现代码如下所示:

db_cursor = db_conn.cursor()

3.执行SQL语句

调用Cursor对象的execute()方法,执行SQL语句,实现对数据库的增删改查操作,代码如下所示:

#新增数据

sql=‘insert into hot(name,author,type,form)values(“道君”,“未知”,“仙侠”,“连载”)’

db_cursor.execute(sql)

#修改数据

sql=‘update hot set author = “跃千愁” where name=“道君”’

db_cursor.execute(sql)

#查询表hot中type为仙侠的数据

sql=‘select * from hot where type=“仙侠”’

db_cursor.execute(sql)

#删除表中type为仙侠的数据

sql=‘delete from hot where type=“仙侠”’

db_cursor.execute(sql)

4.回滚

db_conn.rollback()#回滚操作

需要注意的是,回滚操作一定要在commit之前执行,否则就无法恢复了。

5.提交数据

调用Connection对象的commit()方法实现数据的提交。

db_conn.commit()

6.关闭游标及数据库

当执行完对数据库的所有操作后,不要忘了关闭游标和数据库对象。

db_cursor.close()#关闭游标

db_conn.close() #关闭数据库

项目:qidian_hot爬取到的小说信息存储于MySQL中

1.配置MySQL数据库信息

2.新建MySQLPipeline类

3.连接MySQL数据库服务器

4.将数据存储于MySQL数据库

5.执行数据库关闭工作

6.启用MySQLPipeline

7.运行爬虫

8.查看结果

navicat中
创建数据库(database):qidian
在数据库下创建表(table):qidianhot

设置表字段:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2RdNBwMH-1601619554331)(/Users/jack/Library/Application Support/typora-user-images/image-20200821001955578.png)]

# pipelines.py

import MySQLdb
from scrapy.exceptions import DropItem

class QidianHotPipeline(object):
    def __init__(self):
        self.author_set = set()


    def process_item(self, item, spider): # 接收spider中的item
        if item["name"] in self.author_set:
            raise DropItem("查找到重复姓名的项目:%s"%item)
        return item

class MySQLPipeline(object):
    def open_spider(self,spider): #spider开始时调用一次
        db_name = spider.settings.get("MYSQL_DB_NAME","qidianhot")
        host = spider.settings.get("MYSQL_HOST","localhost")
        user = spider.settings.get("MYSQL_USER","root")
        pwd = spider.settings.get("MYSQL_PASSWORD","mysql1234")
        # 连接MySQL数据库
        self.db_conn = MySQLdb.connect(db=db_name,
                                       host = host,
                                       user=user,
                                       password=pwd,
                                       charset="utf8")
        self.db_cursor = self.db_conn.cursor() #得到游标

    def process_item(self,item,spider): #处理每一个item
        values = (item["name"],item["author"],item["type"],item["form"])
        # 确定SQL
        sql = "insert into qidianhot(name,author,type,form) values(%s,%s,%s,%s)"
        self.db_cursor.execute(sql,values)
        return item

    def close_spider(self,spider): #spider结束时,调用一次
        self.db_conn.commit() # 提交数据
        self.db_cursor.close()
        self.db_conn.close()
# settings.py

# 激活pipelines
ITEM_PIPELINES = {
     
   'qidian_hot.pipelines.QidianHotPipeline': 300,
   'qidian_hot.pipelines.MySQLPipeline': 400,
}

# mysql设置
MYSQL_DB_NAME = "qidian"
MYSQL_HOST = "localhost"
MYSQL_USER = "root"
MYSQL_PASSWORD = "mysql1234"

MongoDB数据库

NoSQL概述

NoSQL全称Not Only SQL,意即“不仅仅是SQL”,泛指非关系型数据库。传统的关系型数据库使用的是固定模式,并将数据分割到各个表中。然而,对大数据集来说,数据量太大使其难以存放在单一服务器中,此时就需要扩展到多个服务器中。不过,关系型数据库对这种扩展的支持并不够好,因为在查询多个表时,数据可能在不同的服务器中。再者,从网络中爬取的数据,不可避免地会存在数据缺失、结构变化的情况。而具有固定模式的关系型数据库很难适应这种情况。相反,NoSQL具有高性能、高可用性和高伸缩性的特点,可用于超大规模数据的存储,而且无需固定的模式,无需多余操作就可以横向扩展。在NoSQL中,有多种方式可以实现非固定模式和横向发展的功能,它们分别是:

列数据存储:如Hbase数据库。

面向文档存储:如MongoDB数据库。

键值对存储:如Redis数据库。

图形存储:如Neo4j数据库。

SQL MongoDB
术语 英文术语 术语 英文术语
数据库 database 数据库 database
table 集合 collection
row 文档 document
column field
索引 index 索引 index
主键 primary key 主键 primary key

Python访问MongoDB数据库

安装访问MongoDB所使用的第三方库pymongo。

​ pip install pymongo

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yj57iByY-1601619554332)(/Users/jack/Library/Application Support/typora-user-images/image-20200822160821069.png)]

1.连接MongoDB数据库服务器

调用方法pymongo.MongoClient()连接MongoDB数据库服务器。连接的地址默认为localhost:27017,也可以手动设置host和port。

import pymongo#导入pymongo库

#方式一:使用默认host和port
db_client = pymongo.MongoClient()

#方式二:自定义host和port
db_client = pymongo.MongoClient(host=“localhost”,port=27017)

#方式三:使用标准的URI连接语法
db_client = pymongo.MongoClient(‘mongodb://localhost:27017/’)

2.指定数据库

MongoDB可以建立多个数据库,因此需要指定要操作的数据库。以下代码指定了名称为“qidian”的数据库:

db = db_client[“qidian”]

3.指定集合(相当于关系型数据库中的表)。

MongoDB中一个数据库可以包含多个集合,这跟关系型数据库中一个数据库有多个表是同一个道理。MongoDB也需要指定要操作的集合。

db_collection = db[“hot”]

4.插入文档

(1)插入与条件匹配的单个文档。

如果想添加一个小说文档到集合hot中,可以先将数据存储于字典中,如下所示:

novel={‘name’: ‘太初’, #名称
‘author’: ‘高楼大厦’,#作者
‘form’: ‘连载’, #形式
‘type’: ‘玄幻’ #类型
}

然后调用db_collection的insert_one()方法将新文档插入到集合hot中,实现代码如下所示:

result = db_collection.insert_one(novel)
print(result)

print(result.inserted_id)

(2)插入与条件匹配的所有文档。

还可以使用insert_many()方法,一次插入多个文档,实现代码如下所示:

novel1={‘name’: ‘丰碑杨门’, #名称
‘author’: ‘圣诞稻草人’,#作者
‘form’: ‘连载’, #形式
‘type’: ‘历史’ #类型
}
novel2={‘name’: ‘帝国的崛起’,#名称
‘author’: ‘终极侧位’, #作者
‘form’: ‘连载’, #形式
‘type’: ‘都市’ #类型
}
result = db_collection.insert_many([novel1,novel2])
print(result)

5.查询文档

可以使用find_one()或find()方法查询集合中的文档记录。find_one()方法返回单个文档记录,而find()方法则返回一个游标对象,用于查询多个文档记录。

result = db_collection.find_one({“name”:“帝国的崛起”})
print(result)

cursor = db_collection.find({})

cursor = db_collection.find({“type”:“历史”})

6.更新文档

可以使用集合的update_one()和update_many()方法实现文档的更新。前者仅更新一个文档;后者可以批量更新多个文档。更新文档的格式如下所示:

{

< operator>: { : , … },

< operator>: { : , … },

}

文档中的operator是MongoDB提供的更新操作符,用于指明更新的方式。例如$set表示修改字段值;$unset表示删除指定字段;$rename表示重命名字段。

7.删除文档

可以使用集合的delete_one()和delete_many()方法实现文档的删除。前者仅删除一个文档;后者可以批量删除多个文档。

result = db_collection.delete_one({“name”:“太初”})

result = db_collection.delete_many({“type”:“历史”})

8.关闭数据库。

当执行完对数据库的所有操作后,不要忘了关闭数据库。

db_client.close()

Redis数据库

配置Redis数据库

1.配置可访问的主机(bind)。

bind 127.0.0.1 192.168.64.100 #配置多个IP,IP之间用空格分开

bind 0.0.0.0 #接受任何网络的连接

2.配置监听端口(port)。

Redis默认的监听端口为6379,可以配置自定义的端口号。

port 6379

3.配置密码(requirepass)。

requirepass foobared

4.配置超时时间(timeout)。

timeout 0

5.配置Redis最大内存容量(maxmemory)。

配置Redis最大的内存容量,注意单位是字节。

maxmemory

6.配置数据库的数量(databases)。

配置数据库的数量,默认的数据库数量是16个,数据库的名称为db0~db15。

databases 16

Python访问Redis数据库

安装访问Redis所需要的第三方库redis-py。

pip install -U redis==2.10.6

Redis操作

1.连接Redis数据库服务器

redis-py库提供两个类Redis和StrictRedis来实现Redis的命令操作。StrictRedis实现了大部分官方的语法和命令,而Redis是StrictRedis的子类,用于向后兼容旧版本的redis-py。这里使用官方推荐的StrictRedis类实现相关操作。

import redis #导入redis模块
#host是redis主机,端口是6379,数据库索引为0,密码为foobared
r = redis.StrictRedis(host=‘localhost’, port=6379,db=0,password=“foobared”)
#将键值对存入redis缓存,key是"name",value是"cathy"
r.set(‘name’, “cathy”)
#取出键name对应的值

print(r[‘name’])
print(r.get(‘name’))

2.字符串(String)操作

字符串是Redis中最基本的键值对存储形式。它在Redis中是二进制安全的,这意味着它可以接受任何格式的数据,如JPEG图像数据或JSON信息等。

import redis #导入redis模块
#生成StrictRedis对象
r = redis.StrictRedis(host=‘localhost’, #主机
port=6379, #端口
db=0, #数据库索引
password=“foobared”, #密码
decode_responses=True)#设置解码
r.set(‘name’, “cathy”) #将值为"cathy"的字符串赋给键name
r.set(“age”,10) #将10赋给age键
r.setnx(“height”,1.50) #如果键height不存在,则赋给值1.50
r.mset({“score1”:100,“score2”:98}) #批量设置

3.列表(List)操作

Redis中的列表是一个双向链表,可以在链表左右分别操作,即支持双向存储。有时也把列表看成一个队列,实现先进先出的功能,所以很多时候将Redis用作消息队列。

r.lpush(“student”,“cathy”,10) #向键为student的列表头部添加值"cathy"和10
r.rpush(“student”,1.50, “女”) #向键为student的列表尾部添加值身高和性别
print(r.lrange(“student”,0,3)) #获取列表student中索引范围是0~3的列表
r.lset(“student”,1,9) #向键为student中索引为1的位置赋值9
r.lpop(“student”) #返回并删除列表student中的首元素
r.rpop(“student”) #返回并删除列表student中的尾元素
r.llen(“student”) #获取student列表长度
print(r.lrange(“student”,0,-1)) #获取列表student中的所有数据

4.无序集合(Set)操作

Redis的Set是由非重复的字符串元素组成的无序集合。

#将"cathy",“tom”,“terry”,“lili”,"tom"5个元素添加到键为names的集合中

r.sadd(“names”,“cathy”,“tom”,“terry”,“lili”,“tom”)
r.scard(“names”) #获取键为names的集合中元素个数,结果为4
r.srem(“names”,“tom”) #从键为names的集合中删除"tom"
r.spop(“names”) #从键为names的集合中随机删除并返回该元素
#将"terry"从键为names的集合中转移到键为names1的集合中

r.smove(“names”,“names1”,“terry”)
r.sismember(“names”,“cathy”)#判断"cathy"是否是键为names的集合中的元素,结果为True
r.srandmember(“names”) #随机获取键为names的集合中的一个元素
print(r.smembers(“names”)) #获取键为names的集合中所有元素

5.散列表(Hash)操作

Redis的散列表可以看成是具有Key-Value键值对的map容器。Redis的Key-Value结构中,Value也可以存储散列表,而Key可以理解为散列表的表名。

#将key为name,value为cathy的键值对添加到键为stu散列表中
r.hset(“stu”,“name”,“cathy”)
r.hmset(“stu”,{“age”:10,“height”:1.50})#批量添加键值对
r.hsetnx(“stu”,“score”,100) #如果score=100的键值对不存在,则添加
r.hget(“stu”,“name”) #获取散列表中key为name的值
r.hmget(“stu”,[“name”,“age”]) #获取散列表中多个key对应的值
r.hexists(“stu”,“name”) #判断key为name的值是否存在,此处为True
r.hdel(“stu”,“score”) #删除key为score的键值对
r.hlen(“stu”) #获取散列表中键值对个数
r.hkeys(“stu”) #获取散列表中所有的key

6.有序集合(Storted Set)操作

与无序集合(Set)一样,有序集合也是由非重复的字符串元素组成的。为了实现对集合中元素的排序,有序集合中每个元素都有一个与其关联的浮点值,称为“分数”。有序集合中的元素按照以下规则进行排序。

①如果元素A和元素B的“分数”不同,则按“分数”的大小排序。

②如果元素A和元素B的“分数”相同,则按元素A和元素B在字典中的排序排列。

CHAPTER6 JavaScript和Ajax数据爬取

JavaScript简介

当我们发现要爬取的数据不在HTML文档中时,就应该考虑到数据可能会通过JavaScript加载。先来了解一下什么是JavaScript。

JavaScript是互联网上最流行的客户端脚本语言。它运行于用户的浏览器中,被广泛用于Web应用开发。它嵌入于HTML中,常用来为HTML网页添加各种动态功能,为用户提供更流畅美观的浏览效果。

JavaScript能够动态加载文本,并将文本嵌入到HTML文档中。而爬虫只关注JavaScript动态加载的文本,因此,JavaScript的运行机制以及语法结构,仅需了解即可。

项目:QQ音乐榜单

QQ音乐是腾讯公司推出的一款网络音乐服务产品,提供海量音乐在线试听、歌词翻译、手机铃声下载、高品质无损音乐试听、音乐下载等服务。图6-1为QQ音乐中流行指数排行榜的页面,网址为https://y.qq.com/n/yqq/toplist/4.html。现要将榜单中的歌曲信息爬取下来,字段有:歌曲名称、唱片、歌手和时长。

1.创建项目

终端

cd 项目文件夹

scrapy startproject QQMusic

settings.py模拟浏览器登录

USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.135 Safari/537.36'
2.使用Item封装数据

Items.py创建所需字段,歌曲名称、唱片、歌手和时长

import scrapy

class QqmusicItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    song_name = scrapy.Field()
    album_name = scrapy.Field()
    singer_name = scrapy.Field()
    interval = scrapy.Field()
3.创建Spider文件及Spider类

通过网页分析发现数据不是存储在html中,而是在XHR中的一个cfg文件中,存储形式为json格式

通过json格式检验,发现数据在列表[“detail”][“data”][“songInfoList”]中,

{
     
    "code":0,
    "ts":1598418464440,
    "start_ts":1598418464432,
    "detail":{
     
        "code":0,
        "data":{
     
            "data":Object{
     ...},
            "songInfoList":[
                {
     
                    "id":272124970,
                    "type":0,
                    "mid":"0013KFa32c9lVn",
                    "name":"不爱我",
                    "title":"不爱我",
                    "subtitle":"",
                    "singer":[
                        {
     
                            "id":5062,
                            "mid":"002J4UUk29y8BY",
                            "name":"薛之谦",
                            "title":"薛之谦",
                            "type":0,
                            "uin":0
                        }
                    ],
                    "album":{
     
                        "id":14253042,
                        "mid":"003tLFOK0wjtKs",
                        "name":"不爱我",
                        "title":"不爱我",
                        "subtitle":"",
                        "time_public":"2020-08-20",
                        "pmid":"003tLFOK0wjtKs_1"
                    },
                    "mv":{
     
                        "id":0,
                        "vid":"",
                        "name":"",
                        "title":"",
                        "vt":0
                    },
                    "interval":264,
                    "isonly":0,
                    "language":0,
                    "genre":1,
                    "index_cd":0,
                    "index_album":1,
                    "time_public":"2020-08-20",
                    "status":31,
                    "fnote":0,
                    "file":{
     
                        "media_mid":"0006kNz63gpDYW",
                        "size_24aac":0,
                        "size_48aac":1606521,
                        "size_96aac":3223171,
                        "size_192ogg":5614611,
                        "size_192aac":6375652,
                        "size_128mp3":4231610,
                        "size_320mp3":10578739,
                        "size_ape":0,
                        "size_flac":24981613,
                        "size_dts":0,
                        "size_try":0,
                        "try_begin":64788,
                        "try_end":95489,
                        "url":"",
                        "size_hires":0,
                        "hires_sample":0,
                        "hires_bitdepth":0,
                        "b_30s":0,
                        "e_30s":0,
                        "size_96ogg":2945136
                    },
                    "pay":{
     
                        "pay_month":0,
                        "price_track":0,
                        "price_album":0,
                        "pay_play":0,
                        "pay_down":0,
                        "pay_status":0,
                        "time_free":0
                    },
                    "action":{
     
                        "switch":605971,
                        "msgid":0,
                        "alert":11,
                        "icons":135752,
                        "msgshare":0,
                        "msgfav":0,
                        "msgdown":0,
                        "msgpay":0
                    },
                    "ksong":{
     
                        "id":12343841,
                        "mid":"003vj5gU0KJcPS"
                    },
                    "volume":{
     
                        "gain":-7.285,
                        "peak":1,
                        "lra":13.44
                    },
                    "label":"0",
                    "url":"",
                    "bpm":0,
                    "version":0,
                    "trace":"",
                    "data_type":0,
                    "modify_stamp":0,
                    "pingpong":"",
                    "aid":0,
                    "ppurl":"",
                    "tid":0,
                    "ov":0,
                    "sa":0,
                    "es":""
                },

spiders中创建music_spider.py

# coding : utf-8

from scrapy import Request
from scrapy.spiders import Spider
from QQMusic.items import QqmusicItem
import json

class MusicSpider(Spider):
    name = "music"
    def start_requests(self):
        url = "https://u.y.qq.com/cgi-bin/musics.fcg?-=getUCGI10191787736996072&g_tk=5381&sign=zzaoda95dtf9vst4hxedab8128e78d7233eac3855fff7429013&loginUin=0&hostUin=0&format=json&inCharset=utf8&outCharset=utf-8¬ice=0&platform=yqq.json&needNewCode=0&data=%7B%22detail%22%3A%7B%22module%22%3A%22musicToplist.ToplistInfoServer%22%2C%22method%22%3A%22GetDetail%22%2C%22param%22%3A%7B%22topId%22%3A4%2C%22offset%22%3A0%2C%22num%22%3A20%2C%22period%22%3A%222020-08-25%22%7D%7D%2C%22comm%22%3A%7B%22ct%22%3A24%2C%22cv%22%3A0%7D%7D"
        yield Request(url)

    def parse(self, response):
        item = QqmusicItem()
        json_text = response.text
        music_dict = json.loads(json_text)
        for one_music in music_dict["detail"]["data"]["songInfoList"]:
            # get song
            item["song_name"] = one_music["title"]
            # get album
            item["album_name"] = one_music["album"]["name"]
            # get singer
            item["singer_name"] = one_music["singer"][0]["name"]
            # get interval
            item["interval"] = one_music["interval"]
            yield item

4.运行爬虫

Start.py

from scrapy import cmdline

cmdline.execute("scrapy crawl music -o music.csv".split())
5.查看结果

Music.csv

album_name,interval,singer_name,song_name
不爱我,264,薛之谦,不爱我
My Boo,169,易烊千玺,My Boo
走下去,209,DP龙猪,走下去
重现8090,209,永彬Ryan.B,异类
乐队的夏天2 第8期,324,木马,后来 (Live)
夏日调色盘,199,树影叶魅,夏日调色盘
可惜我是水瓶座,249,吴业坤,可惜我是水瓶座
陪我去流浪,246,阿悄,海海海
2020中国好声音 第1期,272,潘虹,最好 (Live)
说唱新世代 第1期,203,于贞,她和她和她 (Live)
乐队的夏天2 第8期,271,野孩子,竹枝词 (Live)
Darling早上好,174,Lee A,Darling早上好
Smile,131,Katy Perry,What Makes A Woman (Explicit)
慢慢,239,Uu,慢慢
我想和妳,165,韩子帆,我想和妳在一起
2020中国好声音 第1期,100,李荣浩,谢谢你的爱1999 (Live)
下落不明(完整版),169,王忻辰,下落不明(完整版)
甜甜咸咸,198,你的大表哥曲甲,甜甜咸咸
2020中国好声音 第1期,233,宋宇宁,三巡 (Live)
2020中国好声音 第1期,142,李健,漂浮地铁 (Live)

Ajax简介

传统的网页,如果需要更新内容,必须重载整个页面。有了Ajax,便可以在不重新加载整个网页的情况下,对网页的某部分进行更新。

Ajax,全称Asynchronous JavaScript and XML,即异步的JavaScript和XML。它不是新的编程语言,而是利用JavaScript在不重新加载整个页面的情况下,与服务器交换数据并更新部分网页内容的技术。

项目:豆瓣电影

现要实现将中国大陆电影信息爬取下来,字段有:电影名称、导演、演员和评分。网址:https://movie.douban.com/tag/#/

1.创建项目

终端

cd 项目文件夹
scrapy startproject Douban

settings.py模拟浏览器登录,取消obey robots

# Crawl responsibly by identifying yourself (and your website) on the user-agent
USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.135 Safari/537.36'

# Obey robots.txt rules
ROBOTSTXT_OBEY = False

2.使用Item封装数据

Items.py创建需求字段:电影名称、导演、演员和评分

import scrapy

class DoubanItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    title = scrapy.Field()
    directors = scrapy.Field()
    casts = scrapy.Field()
    rate = scrapy.Field()
    pass
3.创建Spider文件及Spider类

url ="https://movie.douban.com/j/new_search_subjects?sort=U&range=0,10&tags=电影&start=0&countries=中国大陆"通过分析url可知,每次请求的url中start以20为基数变化

Douban_spiders.py

# coding: utf-8

from scrapy import Request
from scrapy.spiders import Spider
from Douban.items import DoubanItem
import json

class DoubanSpider(Spider):
    name = "douban"
    current_page = 1

    def start_requests(self):
        url = "https://movie.douban.com/j/new_search_subjects?sort=U&range=0,10&tags=电影&start=0&countries=中国大陆"
        yield Request(url)

    def parse(self, response):
        item = DoubanItem()
        json_text = response.text
        movies_dict = json.loads(json_text)

        if len(movies_dict["data"]) == 0:
            return
        for one_movie in movies_dict["data"]:
            item["title"] = one_movie["title"]
            item["directors"] = one_movie["directors"]
            item["casts"] = one_movie["casts"]
            item["rate"] = one_movie["rate"]
            yield item

        # 抓取下一页信息,直至无下一页,程序报错
        url_next = "https://movie.douban.com/j/new_search_subjects?sort=U&range=0,10&tags=电影&start=%d&countries=中国大陆"%(self.current_page*20)
        self.current_page += 1
        yield Request(url_next)
4.运行爬虫

Start.py

from scrapy import cmdline
cmdline.execute("scrapy crawl douban -o douban.csv".split())
5.查看结果

Doubt.csv

casts,directors,rate,title
"徐峥,王传君,周一围,谭卓,章宇",文牧野,9.0,我不是药神
"吕艳婷,囧森瑟夫,瀚墨,陈浩,绿绮",饺子,8.5,哪吒之魔童降世
"周冬雨,易烊千玺,尹昉,周也,吴越",曾国祥,8.3,少年的你
"姜文,葛优,周润发,刘嘉玲,陈坤",姜文,8.8,让子弹飞
"张国荣,张丰毅,巩俐,葛优,英达",陈凯歌,9.6,霸王别姬
"屈楚萧,吴京,李光洁,吴孟达,赵今麦",郭帆,7.9,流浪地球
"黄渤,张译,韩昊霖,杜江,葛优","陈凯歌,张一白,管虎,薛晓路,徐峥,宁浩,文牧野",7.7,我和我的祖国

CHAPTER7 动态渲染页面爬取

很多接口地址既冗长又复杂,有的还经过加密甚至还有时效性。例如今日头条的新闻信息的接口地址为https://www.toutiao.com/api/pc/feed/?category=news_hot&utm_source=toutiao&widen=1&max_behot_time=0&max_behot_time_tmp=0&tadrequire=true&as=A195DBDC06ADC25&cp=5BC65DAC62854E1&_signature=ZtN.cQAAPRnM.D.xF5yhvGbTf2,从中很难找出规律,也就无法爬取更多的新闻信息。

Selenium实现动态页面爬取

环境搭建:Scrapy+Selenium+PhantomJS

安装selenium
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple selenium
安装chromedriver

安装浏览器驱动程序,需要下载一个Selenium调用浏览器的驱动文件。以Chrome浏览器为例。

1.下载与浏览器版本对应的驱动文件。Chromedriver的下载地址:

官方下载地址:https://chromedriver.storage.googleapis.com/index.html

其他下载地址:http://npm.taobao.org/mirrors/chromedriver/

2.解压出来的文件放入usr/local/bin指定目录下

1.打开一个Finder,然后command+shift+G
2.输入/usr/local/bin
3.下载后解压出来的文件拷贝进入usr/local/bin目录
4.输入chromedriver --version检查一下

3.环境配置

打开终端,输入: cd ~ 回车,会进入~文件夹 
然后输入:touch .bash_profile,回车执行后, 
再输入:open -e .bash_profile 回车
会在TextEdit中打开这个文件,输入:export PATH=$PATH:/usr/local/bin/ChromeDriver,然后保存
安装phantomJS

phantomJS是一个无界面浏览器

brew cask install phantomjs

由于Selenium宣称不再支持PhantomJS,因此如果你的Selenium版本较高,运行本项目就会出错。我们建议将PhantomJS换成Firefox浏览器(新版本的Chrome对Selenium也做了限制,不建议使用Chrome浏览器),方法为:
1.下载并安装火狐浏览器(http://www.firefox.com.cn/)
2.下载火狐浏览器对应版本的驱动geckodriver并将geckodriver.exe文件放置到anaconda3/Scripts目录下。
(geckodriver下载https://github.com/mozilla/geckodriver/releases)
3.本项目只需要修改一行代码:
在toutiao_spider.py中,将:
self.driver = webdriver.PhantomJS()
更改为:
self.driver = webdriver.Firefox()

Selenium语法

1.声明浏览器对象

Selenium支持很多浏览器,如Chrome、Firefox、IE、Opera、Safari等;也支持Android、BlackBerry等手机端的浏览器;

from selenium import webdriver
driver = webdriver.Chrome()  #声明Chrome浏览器对象
driver = webdriver.ie()       #声明ie浏览器对象
driver = webdriver.firefox()   #声明firefox浏览器对象
driver = webdriver.phantomjs()#声明phantomjs浏览器对象
driver = webdriver.safari()    #声明safari浏览器对象

2.访问页面

首先想到的就是使用driver在浏览器中打开一个链接,可以使用get()方法实现:

driver.get("https://www.suning.com/")#请求页面

3.获取页面代码

访问页面后,就可以使用driver的page_source属性获取页面的HMTL代码了:

#获取代码
 HTML=driver.page_source

4.定位元素

当获取到HTML代码后,就需要定位到HTML的各个元素,以便提取数据或者对该元素执行诸如输入、点击等操作。WebDriver 提供了大量的方法查询页面中的节点,这些方法形如:find_element_by_*。

以下为Selenium查找单个节点的方法。

find_element_by_id:通过id查找。
find_element_by_name:通过name查。
find_element_by_xpath:通过xpath选择器查找。
find_element_by_link_text:通过链接的文本查找(完全匹配)。
find_element_by_partial_link_text:通过链接的文本查找(部分匹配)。
find_element_by_tag_name:通过标签名查找。
find_element_by_class_name:通过class查找。
find_element_by_css_selector:通过css选择器查找。

5.页面交互

Selenium可以模拟用户对页面执行一系列操作,如输入数据、清除数据、单击按钮等。

from selenium import webdriver
from selenium.webdriver.common.keys
import Keys  #导入Keys类
driver = webdriver.Chrome()                     #声明Chrome浏览器对象
driver.get("https://www.suning.com/")             #请求页面
input = driver.find_element_by_id("searchKeywords")#查找节点
input.clear()                                  #清除输入框中默认文字
input.send_keys("iphone")                      #输入框中输入“iphone”
input.send_keys(Keys.RETURN)                #回车功能

6.执行JavaScript

Selenium并未提供所有的页面交互操作方法,例如爬虫中用得最多的下拉页面(用于加载更多内容)。不过Selenium提供了execute_script()方法,用于执行JS,这样我们就可以通过JS代码实现这些操作了。

7.等待页面加载完成

Selenium中跟超时和等待有关的方法主要有三个。

  • 等待超时:set_page_load_timeout()

driver的set_page_load_timeout()方法用于设置页面完全加载的超时时间

  • 隐式等待:implicitly_wait()

    隐式等待的方法为implicitly_wait(),用于设定一个数据加载的最长等待时间。

  • 显式等待:WebDriverWait类

    显性等待使用Selenium的WebDriverWait类,配合该类的until()方法,就能够根据判断条件而进行灵活地等待了。

项目:今日头条

今日头条热点新闻的页面如图所示,网址为https://www.toutiao.com/ch/news_hot/。

页面默认显示20条热点新闻信息,将页面拉到最底端,会再加载20条信息。因此,如果想要查看更多热点新闻,就必须不断下拉页面。本项目希望使用网络爬虫技术,将尽量多的热点新闻爬取下来保存于CSV文件中。

爬取的字段有:新闻标题、新闻来源和评论数。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-s7r7MiSr-1601619554334)(/Users/jack/Library/Application Support/typora-user-images/image-20200827220830228.png)]

1.创建项目

终端

cd 项目文件夹
scrapy startproject Toutiao

settings.py模拟浏览器登录,取消obey robots,打开Middlewares

# Crawl responsibly by identifying yourself (and your website) on the user-agent
USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.135 Safari/537.36'

# Obey robots.txt rules
ROBOTSTXT_OBEY = False

# Enable or disable spider middlewares
SPIDER_MIDDLEWARES = {
     
   'Toutiao.middlewares.ToutiaoSpiderMiddleware': 543,
}
2.使用Item封装数据

Items.py创建需求字段:新闻标题、新闻来源和评论数。

import scrapy

class ToutiaoItem(scrapy.Item):
    title = scrapy.Field()
    source = scrapy.Field()
    comment = scrapy.Field()
3.使用DownloaderMiddleware处理request

对https://www.toutiao.com/ch/news_hot/网页分析发现,鼠标每次滚动至页面下部时会加载新的新闻条目

middlewares.py

# -*- coding: utf-8 -*-
from scrapy import signals
import time # 导入时间模块
from scrapy.http import HtmlResponse #导入HTML响应模块
from selenium.webdriver.common.by import By #导入By模块
from selenium.webdriver.support.wait import WebDriverWait #导入等待模块
from selenium.webdriver.support import expected_conditions as EC #导入预期条件模块
from selenium.common.exceptions import TimeoutException,NoSuchElementException #导入异常模块

···

    def process_request(self, request, spider):
        # 判断name是否是toutiao的爬虫
        if spider.name == "toutiao":
            spider.deiver.get(request.url)
            try:
                wait = WebDriverWait(spider.driver,5) #设置显式等待,最多5秒
                wait.until(EC.presence_of_element_located(By.XPATH,".//div[@class='wcommonFeed']")) #等待新闻列表容器加载完成
                time.sleep(5)
                spider.driver.execute_script("window.scrollTo(0,document.body.scrollHeight/2)") #使用js的scrollTo方法实现将页面向下滚动到中间
                for i in range(10):
                    time.sleep(5)
                    spider.driver.execute_script("window.scollTo(0,document.body.scrollHeight)") #使用js的scrollTo方法将页面滚动到最底端
                origin_code = spider.driver.page_source #获取加载完成的页面源代码
                res = HtmlResponse(url=request.url,encoding="utf-8",body=origin_code,request=request) # 将源代码构造成为一个Response对象,并返回。
                return res
            except TimeoutException:
                print("time out")
            except NoSuchElementException:
                print("No such element.")
        return None
      
···
4.创建Spider文件及Spider类

url ="https://movie.douban.com/j/new_search_subjects?sort=U&range=0,10&tags=电影&start=0&countries=中国大陆"通过分析url可知,每次请求的url中start以20为基数变化

Douban_spiders.py

# coding: utf-8

from scrapy import Request
from scrapy.spiders import Spider
from Douban.items import DoubanItem
import json

class DoubanSpider(Spider):
    name = "douban"
    current_page = 1

    def start_requests(self):
        url = "https://movie.douban.com/j/new_search_subjects?sort=U&range=0,10&tags=电影&start=0&countries=中国大陆"
        yield Request(url)

    def parse(self, response):
        item = DoubanItem()
        json_text = response.text
        movies_dict = json.loads(json_text)

        if len(movies_dict["data"]) == 0:
            return
        for one_movie in movies_dict["data"]:
            item["title"] = one_movie["title"]
            item["directors"] = one_movie["directors"]
            item["casts"] = one_movie["casts"]
            item["rate"] = one_movie["rate"]
            yield item

        # 抓取下一页信息,直至无下一页,程序报错
        url_next = "https://movie.douban.com/j/new_search_subjects?sort=U&range=0,10&tags=电影&start=%d&countries=中国大陆"%(self.current_page*20)
        self.current_page += 1
        yield Request(url_next)
4.运行爬虫

Start.py

from scrapy import cmdline
cmdline.execute("scrapy crawl douban -o douban.csv".split())
5.查看结果

Doubt.csv

casts,directors,rate,title
"徐峥,王传君,周一围,谭卓,章宇",文牧野,9.0,我不是药神
"吕艳婷,囧森瑟夫,瀚墨,陈浩,绿绮",饺子,8.5,哪吒之魔童降世
"周冬雨,易烊千玺,尹昉,周也,吴越",曾国祥,8.3,少年的你
"姜文,葛优,周润发,刘嘉玲,陈坤",姜文,8.8,让子弹飞
"张国荣,张丰毅,巩俐,葛优,英达",陈凯歌,9.6,霸王别姬
"屈楚萧,吴京,李光洁,吴孟达,赵今麦",郭帆,7.9,流浪地球
"黄渤,张译,韩昊霖,杜江,葛优","陈凯歌,张一白,管虎,薛晓路,徐峥,宁浩,文牧野",7.7,我和我的祖国

你可能感兴趣的:(Python-scrapy爬虫)