【爬虫】4.5 实践项目——爬取当当网站图书数据

目录

1. 网站图书数据分析

2. 网站图书数据提取

3. 网站图书数据爬取

(1)创建 MySQL 数据库

(2)创建 scrapy 项目

(3)编写 items.py 中的数据项目类

(4)编写 pipelines_1.py 中的数据处理类

(5)编写 pipelines_2.py 中的数据处理类

(6)编写 Scrapy 的配置文件

(7)编写 Scrapy 爬虫程序

(8)执行 Scrapy 爬虫程序


实践内容:Scrapy框架+Xpath信息提取方法设计商城(这里用的当当网)商品信息网站及爬虫程序,以关键字“书包”(python)搜索页面的商品,爬取(学号相关的特定某几个页面(最后一位,页面大于3)及限定数量商品(最后3位))商品信息。

编程思路:

1. 功能描述

  • 输入:需要爬取的商品与学号
  • 输出:书本信息并保存的MySQL中

2. 程序的结构设计

  • 从当当网上获取数据:使用scripy框架,使用xpath查找html元素
  • 下面两个特定数量爬取写了两个管道 pipelines_1.py, pipelines_2.py
  • 爬取1:(最后一位,页面大于3)——>(3,>3)并输出到MySQL中,open_scripy,把数据INSERT到数据库中,close_scripy
  • 爬取2:(最后3位)——>103条数据,并输出到MySQL,open_scripy,把数据INSERT到数据库中,close_scripy

1. 网站图书数据分析

        当当图书网站是国内比较大型的图书网站,这个项目的目的就是对该网站的某个主题的一类图书的数据的爬取,把爬取的数据存储到MySQL数据库中。

        例如我们关心Python类的图书,想知道网站上有什么Python的图书,用 Chrome浏览器进入当当网站,在搜索关键字中输入”Python”搜索得到 Python的图书,地址转为:

http://search.dangdang.com/?key=Python&act=input

这类的图书很多,点击“下一页”后地址转为:

http://search.dangdang.com/?key=Python&act=input&page_index=2

        从地址上我们知道知道搜索的关键字是key参数,页码参数是page_index, 而act=input参数只是表明是通过输入进行的查询。

        网页元素分析,为后面使用Xpath查找做准备

【爬虫】4.5 实践项目——爬取当当网站图书数据

         仔细分析 HTML 代码结构,可以看到每本书都是一个

  • 的项目,而且它们的结构完全是一样的,这些
  • 包含在一个
      中。

              在代码中选择第一个

    • ,点击鼠标右键弹出菜单,执行”Edit as HTML” 进入文本编辑,复制出一本书
    • 项目的代码,这段代码放到记事本中, 保存为book.txt文本文件时提示包含Unicode编码字符或者utf-16,于是按要求以 Unicode编码保存为book.txt文件。然后编写一小段程序用BeautifulSoup 装载:

      BeautifulSoup 装载 Test1.py 如下:

      # BeautifulSoup 装载
      from bs4 import BeautifulSoup
      
      fobj = open("book.txt", "rb")
      data = fobj.read()
      fobj.close()
      data = data.decode("utf-16")
      soup = BeautifulSoup(data, "lxml")
      print(soup.prettify())
      

       通过 prettify 整理后就可以清晰看到

    • 层次结构,结果如下:

       

       

    •    

           Python 算法教程

         

         

         

         

         

           

            Python

           

           算法教程

         

         

         

          精通Python基础算法 畅销书Python基础教程作者力作

         

         

         

           ¥51.75

         

         

           定价:

         

         

           ¥69.00

         

         

           (7.5折)

         

         

         

         

         

         

           人民邮电出版社官方旗舰店

         

         

         

         

         

         

           

           

         

         

           8条评论

         

         

         

         

         

         

           [挪威]

           

            Magnus

           

           Lie

           

            Hetland

           

           

            赫特兰

           

         

         

           /2016-01-01

         

         

           /

           

            人民邮电出版社

           

         

         

         

         

           

            加入购物车

           

           

            收藏

           

         

         

       

    •  

      2. 网站图书数据提取

              假定只关心图书的名称title、作者author、出版时间date、出版 社publisher、价格price以及书的内容简介detail,那么用book.txt存储的代码来测试获取的方法。从book.txt中的代码的分析,我们可以编写 test.py 程序获取这些数据.

      图书数据获取 Test2.py 如下:

      # 图书数据获取
      from bs4 import BeautifulSoup
      from bs4.dammit import UnicodeDammit
      import scrapy
      
      
      class TestItem:
          def __init__(self):
              self.title = ""
              self.author = ""
              self.date = ""
              self.publisher = ""
              self.price = ""
              self.detail = ""
      
          def show(self):
              print(self.title)
              print(self.author)
              print(self.date)
              print(self.price)
              print(self.publisher)
              print(self.detail)
      
      
      try:
          # 这段程序从book.txt中装载数据,并识别它的编码,生成Selector对象,并由此找到
    • 元素节点。 fobj = open("book.txt", "rb") data = fobj.read() fobj.close() dammit = UnicodeDammit(data, ["utf-8", "utf-16", "gbk"]) data = dammit.unicode_markup selector = scrapy.Selector(text=data) li = selector.xpath("//li") #
    • 中有多个,从HTML代码可以看到书名包含在第一个的title属性中, # 因此通过position()=1找出第一个,然后取出title属性值就是书名title。 title = li.xpath("./a[position()=1]/@title").extract_first() # 价钱包含在
    • 中的class='price'的

      元素下面的 class='search_now_price'的元素的文本中。 price = li.xpath("./p[@class='price']/span[@class='search_now_price']/text()").extract_first() # 作者包含在

    • 下面的class='search_book_author'的

      元素下面的第一个 # 元素的title属性中,其中span[position()=1]就是限定第一个 。 author = li.xpath("./p[@class='search_book_author']/span[position()=1]/a/@title").extract_first() # 出版日期包含在

    • 下面的class='search_book_author'的

      元素下面的倒数第二个元素的文本中, # 其中span[position()=last()-1]就是限定倒数第二个 ,last()是最后一个的序号。 date = li.xpath("./p[@class='search_book_author']/span[position()=last()-1] / text()").extract_first() # 出版社包含在

    • 下面的class='search_book_author'的

      元素下面的最 后一个元素的title属性中, # 其中span[position()=last()]就是最后一 个 ,last()是最后一个的序号。 publisher = li.xpath("./p[@class='search_book_author']/span[position()=last()]/a/@title").extract_first() # 在

    • 下面的class='detail'的

      的文本就是书的简介。 detail = li.xpath("./p[@class='detail']/text()").extract_first() item = TestItem() # 无论是哪个数据存在, 那么extract_first()就返回这个数据的值, # 如果不存在就返回None,为了避免出现None的值,我们把None转为空字符串。 item.title = title.strip() if title else "" item.author = author.strip() if author else "" # 从HTML中看到日期前面有一个符号"/",因此如果日期存在时就把这个前导的符号"/"去掉。 item.date = date.strip()[1:] if date else "" item.publisher = publisher.strip() if publisher else "" item.price = price.strip() if price else "" item.detail = detail.strip() if detail else "" item.show()except Exception as err: print(err)

    • 程序执行结果:

      Python 算法教程

      [挪威] Magnus Lie Hetland 赫特兰

      2016-01-01

      ¥51.75

      人民邮电出版社

      精通Python基础算法 畅销书Python基础教程作者力作

      3. 网站图书数据爬取

      (1)创建 MySQL 数据库

      注意:下面创建数据库与数据表,已在 pipelines.py 中编写了

      在 MySQL 中创建数据库 scripy, 创建2个图书表books如下:

      CREATE DATABASE scripy;
      
      CREATE TABLE  books(
          bTitle VARCHAR(512),
          bAuthor VARCHAR(256),
          bPublisher VARCHAR(256),
          bDate VARCHAR(32),
          bPrice VARCHAR(16),
          bDetail text
      );

      (2)创建 scrapy 项目

      scrapy startproject Project_books

      (3)编写 items.py 中的数据项目类

      # Define here the models for your scraped items
      #
      # See documentation in:
      # https://docs.scrapy.org/en/latest/topics/items.html
      
      import scrapy
      
      
      class BookItem(scrapy.Item):
          # define the fields for your item here like:
          title = scrapy.Field()
          author = scrapy.Field()
          date = scrapy.Field()
          publisher = scrapy.Field()
          detail = scrapy.Field()
          price = scrapy.Field()

      (4)编写 pipelines_1.py 中的数据处理类

      # Define your item pipelines here
      #
      # Don't forget to add your pipeline to the ITEM_PIPELINES setting
      # See: https://docs.scrapy.org/en/latest/topics/item-pipeline.html
      
      
      # useful for handling different item types with a single interface
      import pymysql
      
      
      class BookPipeline(object):
          def open_spider(self, spider):
              print("opened_爬取1")
              try:
                  self.con = pymysql.connect(host="127.0.0.1", port=3306, user='root', password="123456", charset="utf8")
                  self.cursor = self.con.cursor(pymysql.cursors.DictCursor)
                  self.cursor.execute("CREATE DATABASE IF NOT EXISTS scripy")
                  self.con = pymysql.connect(host="127.0.0.1", port=3306, user='root', password="123456", db='scripy',
                                             charset="utf8")
                  self.cursor = self.con.cursor(pymysql.cursors.DictCursor)
                  self.cursor.execute("CREATE TABLE IF NOT EXISTS books_1("
                                      "bTitle VARCHAR(512),"
                                      "bAuthor VARCHAR(256),"
                                      "bPublisher VARCHAR(256),"
                                      "bDate VARCHAR(32),"
                                      "bPrice VARCHAR(16),"
                                      "bDetail text)")
                  self.cursor.execute("DELETE FROM books_1")
      
                  self.opened = True
                  self.count_1 = 0
              except Exception as err:
                  print(err)
                  self.opened = False
      
          def close_spider(self, spider):
              if self.opened:
                  self.con.commit()
                  self.con.close()
                  self.opened = False
              print("closed_爬取1")
              print(f"总共爬取{self.count_1}本书籍")
      
          def process_item(self, item, spider):
              try:
                  print(item["title"])
                  print(item["author"])
                  print(item["publisher"])
                  print(item["date"])
                  print(item["price"])
                  print(item["detail"])
                  print()
                  if self.opened:
                      self.cursor.execute("INSERT INTO books_1(bTitle,bAuthor,bPublisher,bDate,bPrice,bDetail)"
                                          "value (%s,%s,%s,%s,%s,%s)",
                                          (item["title"], item["author"], item["publisher"],
                                           item["date"], item["price"], item["detail"]))
                      self.count_1 += 1
              except Exception as err:
                  print(err)
              # spider.crawler.engine.close_spider(spider, "无有效信息,关闭spider")  # pepline 中使用此关闭方法
              return item
      

      (5)编写 pipelines_2.py 中的数据处理类

      # Define your item pipelines here
      #
      # Don't forget to add your pipeline to the ITEM_PIPELINES setting
      # See: https://docs.scrapy.org/en/latest/topics/item-pipeline.html
      
      
      # useful for handling different item types with a single interface
      import pymysql
      
      
      class Input_message:
          key = input('请输入需要爬取当当网的某类书籍:')
          id = input("请输入学号:")  # 102002103
          page = id[-1]  # 爬取1-->第3页开始,爬取大于3页结束
          page_1 = int(input(f"从第{page}开始,爬取__页(请大于3页):"))
          num = id[-3:]  # 爬取2-->103件商品
      
      
      class BookPipeline(object):
          def open_spider(self, spider):
              print("opened_爬取2")
              try:
                  self.con = pymysql.connect(host="127.0.0.1", port=3306, user='root', password="123456", charset="utf8")
                  self.cursor = self.con.cursor(pymysql.cursors.DictCursor)
                  self.cursor.execute("CREATE DATABASE IF NOT EXISTS scripy")
                  self.con = pymysql.connect(host="127.0.0.1", port=3306, user='root', password="123456", db='scripy',
                                             charset="utf8")
                  self.cursor = self.con.cursor(pymysql.cursors.DictCursor)
                  self.cursor.execute("CREATE TABLE IF NOT EXISTS books_2("
                                      "bTitle VARCHAR(512),"
                                      "bAuthor VARCHAR(256),"
                                      "bPublisher VARCHAR(256),"
                                      "bDate VARCHAR(32),"
                                      "bPrice VARCHAR(16),"
                                      "bDetail text)")
                  self.cursor.execute("DELETE FROM books_2")
                  self.opened = True
                  self.count_2 = 0
              except Exception as err:
                  print(err)
                  self.opened = False
      
          def close_spider(self, spider):
              if self.opened:
                  self.con.commit()
                  self.con.close()
                  self.opened = False
              print("closed_爬取2")
              print(f"总共爬取{self.count_2}本书籍")
      
          def process_item(self, item, spider):
              try:
                  print(item["title"])
                  print(item["author"])
                  print(item["publisher"])
                  print(item["date"])
                  print(item["price"])
                  print(item["detail"])
                  print()
                  if self.opened:
                      self.cursor.execute("INSERT INTO books_2(bTitle,bAuthor,bPublisher,bDate,bPrice,bDetail)"
                                          "value (%s,%s,%s,%s,%s,%s)",
                                          (item["title"], item["author"], item["publisher"],
                                           item["date"], item["price"], item["detail"]))
                      self.count_2 += 1
                      if self.count_2 == int(Input_message.num):  # 学号后3为
                          BookPipeline.close_spider(self, spider)
              except Exception as err:
                  print(err)
              # spider.crawler.engine.close_spider(spider, "无有效信息,关闭spider")  # pepline 中使用此关闭方法
              return item
      

              在scrapy的过程中一旦打开一个 spider 爬虫, 就会执行这个类的 open_spider(self,spider) 函数,一旦这个 spider 爬虫关闭, 就执行这个类的 close_spider(self,spider) 函数。因此程序在open_spider 函数中连接 MySQL数据库,并创建操作游标 self.cursor,在close_spider中提交数 据库并关闭数据库,程序中使用 count 变量统计爬取的书籍数量。 在数据处理函数中每次有数据到达,就显示数据内容,并使用 insert 的SQL语句把数据插入到数据库中。

      (6)编写 Scrapy 的配置文件settings.py

      ITEM_PIPELINES = {
         "Project_books.pipelines_1.BookPipeline": 300,
         "Project_books.pipelines_2.BookPipeline": 300,
      }

              简单的配置 settings,这样就可以把爬取的数据推送到管道的BookPipeline类中。

      (7)编写 Scrapy 爬虫程序MySpider.py

      import scrapy
      from ..items import BookItem
      from bs4.dammit import UnicodeDammit
      from ..pipelines_2 import Input_message
      
      
      class MySpider(scrapy.Spider):
          name = "mySpider"
          source_url = "https://search.dangdang.com/"
          act = '&act=input&page_index='
      
          # 以下信息写道pipelines2里了
          # id = input("请输入学号:")  # 102002103
          # page = id[-1]  # 爬取1-->第3页开始,爬取大于3页结束
          # page_1 = int(input(f"从第{page}开始,爬取__页(请大于3页):"))
          # num = id[-3:]  # 爬取2-->103件商品
          # 指明要爬取的网址
          def start_requests(self):
              # url = 'http://search.dangdang.com/?key=Python&act=input&page_index=2'
              url = MySpider.source_url + "?key=" + Input_message.key + MySpider.act + Input_message.page
              yield scrapy.Request(url=url, callback=self.parse)
      
          # 回调函数
          def parse(self, response, **kwargs):
              try:
                  dammit = UnicodeDammit(response.body, ["utf-8", "gbk"])
                  data = dammit.unicode_markup
                  selector = scrapy.Selector(text=data)
                  lis = selector.xpath("//li['@ddt-pit'][starts-with(@class,'line')]")
                  for li in lis:
                      title = li.xpath("./a[position()=1]/@title").extract_first()
                      price = li.xpath("./p[@class='price']/span[@class='search_now_price']/text()").extract_first()
                      author = li.xpath("./p[@class='search_book_author']/span[position()=1]/a/@title").extract_first()
                      date = li.xpath("./p[@class='search_book_author']/span[position()=last()-1]/text()").extract_first()
                      publisher = li.xpath(
                          "./p[@class='search_book_author']/span[position()=last()]/a/@title").extract_first()
                      detail = li.xpath("./p[@class='detail']/text()").extract_first()
                      # detail 有时没有,结果None
                      item = BookItem()
                      item["title"] = title.strip() if title else ""
                      item["author"] = author.strip() if author else ""
                      item["date"] = date.strip()[1:] if date else ""
                      item["publisher"] = publisher.strip() if publisher else ""
                      item["price"] = price.strip() if price else ""
                      item["detail"] = detail.strip() if detail else ""
                      yield item
                      # 最后一页时 link 为None
                      # 1.连续爬取不同的页
                      # link = selector.xpath("//div[@class='paging']/ul[@name='Fy']/li[@class='next']/a/@href").extract_first()
                      # if link:
                      #     url = response.urljoin(link)
                      #     yield scrapy.Request(url=url, callback=self.parse)
                      # 2.翻页(学号最后一位+1,学号最后一位+input > 3)
                      for i in range(int(Input_message.page) + 1, int(Input_message.page) + Input_message.page_1):
                          url = MySpider.source_url + "?key=" + Input_message.key + MySpider.act + str(i)
                          yield scrapy.Request(url, callback=self.parse)
              except Exception as err:
                  print(err)
      

              分析网站的HTML代码发现在一个 的元素中包含了翻页的 信息,下面的

        下面的