爬取当当图书排行榜(榜单自选),格式:爬取结果包含但不限于[排名 书名 作者],注意输出格式对齐

爬取当当图书排行榜

一个课堂作业,使用re,BeautifulSoup等模板爬取当当图书排行榜(榜单自选),本篇重点在输出格式对齐那里,完整代码在结尾。

查看当当图书排行榜,分析其网址各部分代表的意义,选取特定分类和时间区间进行爬取

当当网榜单截图

进入多个页面,仔细分析网址变化,可以发现网址最后的‘01.03.52.00.00.00’代表着书的类别,这个分类是影视小说排行榜;‘-year-’或者‘-month-’代表按照年份或者月份的排行榜;‘2020-0-1’是起始时间;最后的数字‘-10’代表当前页面,每页有20本图书。

我们用kind代表书的分类,time代表时间区间,depth代表共读取的页数,则” ‘http://bang.dangdang.com/books/bestsellers/’ + kind + time + ‘-’ + str(i+1) ”即是网址,循环爬取可以得到所有页面的数据。

在这里插入代码片
def main():
    kind = '01.03.52.00.00.00'#排行榜书的类别,这个种类是影视小说
    time = '-year-2020-0-1'#2020年整年的排行榜
    depth = 10#页数
    start_url = 'http://bang.dangdang.com/books/bestsellers/' + kind + time + '-'
    infoList = []
    for i in range(depth):
        try:
            url = start_url + str(i+1)
            html = getHTMLText(url)
            parsePage(infoList, html)
        except:
            continue
    printGoodsList(infoList)

解析网页内容,查看所需信息(排名,书名,作者,出版社,价格)的位置;

爬取网页,使用 BeautifulSoup 进行解析,将代码写入到同目录下 “ceshi.txt”文件中

def getHTMLText(url):
    try:
        r = requests.get(url, timeout=100)
        r.raise_for_status()
        r.encoding = 'GBK'
        #r.encoding = 'UTF-8'
        return r.text
    except:
        return ""

def parsePage(ilt, html):
    try:
        soup = BeautifulSoup(html, 'html.parser')
        '''
        f = open("ceshi.txt", 'w', encoding="UTF-8")
        f.write(str(soup))
        f.close()
        '''

查看网页代码,可以看出书名在

标签下;
作者和出版社在
标签下,我们只是想要作品数据,因此可以每次跳过出版社;当当价和定价分别在 标签下。
网页代码截图
分别爬取书名,作者,当当价和定价。由于书名中可能含有很多介绍性话语,如书名“希腊人左巴(三毛、村上春树生命中的过客,奥斯卡金像奖影片原著。“如果叫我在世界上选择一位导师的话,我肯定选择左巴,他教给了我热爱”我们使用split进行剔除,选择‘,’,‘空格’,‘(’依次进行分割,再选择第一段,我们就得到了书名。最后还要将‘——’替换为‘–’,因为python的print在打印中文破折号时距离不正确,会导致无法对其,因此我们将其更换为英文破折号。对齐问题后面会详细提到。

看代码可以看到我设置了许多过滤条件,这实在是因为爬取到的书名后缀实在太多了,比如后来时间都与你有关(肖战李现同款书,韩寒 何炅 谢娜 吴昕 戚薇 阚清子等明星推荐),再比如请和我谈一场这样的恋爱吧!(一本甜到少女心炸裂、撩到大脑充血的恋爱书!随书附赠一套心灵糖分补充卡+恋爱大作战挑战卡),一个书名你搞那么长介绍干嘛啦,不过我还真有点想看的冲动(doge。

处理作者时,类似的先进行分割,去除介绍性话语,再将网页上错误显示的‘?’更改为’·’;
价格可以直接选择,然后将其加入到列表ilt中。
网页上的‘?’

names = soup.find_all('div', attrs = {'class': 'name'})
        writers = soup.find_all('div', attrs={'class': 'publisher_info'})
        price_ns = soup.find_all('span', 'price_n')
        price_rs = soup.find_all('span', 'price_r')
        for i in range(len(names)):
            publisher = writers[2*i].text.split('(')[0].split(' ')[0].split(',')[0].split(',')[0].replace('?','·')
            title = names[i].text.split('(')[0].split('(')[0].split(' ')[0].split('(')[0].replace('——','--')
            price_n = price_ns[i].string
            price_r = price_rs[i].string
            #print(price_n, price_r)
            ilt.append([publisher, title, price_n, price_r])

筛选所需数据,输出并保存,尝试输出对齐

对列表中保存的数据进行打印,并保存到文本文档中。这个时候遇到了本次实验最大问题:输出无法对齐。
如下图,在使用format尝试对齐时,发现中英文字符混杂的书名和作者会导致无法对齐。
(如果只包含中文,输出还是可以对齐的,把空格改为全角空格即可)

在这里插入图片描述
在这里插入图片描述
修改为全角空格填充,依然没用。
在这里插入图片描述
既然自动填充空格无效,我们尝试编写函数手动填充空格。查阅资料可知,中文字符在UTF8编码下占据三个字节,英文字符在UTF8编码下占据一个字节。因此我们在打印一个字符串之前,可以通过遍历字符串,计算其中中文字符和英文字符各自的数目。
(gbk编码下中文字符占据两个字节)

文本编辑器中,中文字符的宽度正好是英文字符的两倍,因此我们通过公式
“width * 2 - sigle - double * 2”就可以计算出应该填充空格的数目。(width是为字符串设定的占用长度)

格式函数代码如下图。再次进行输出,依然无法对齐。
在这里插入图片描述
在这里插入图片描述
仔细查看输出结果,发觉在python的打印中,中文字符的宽度并不等于英文字符的两倍
而是8:5的关系!!
(笔者有种想杀人的冲动!!!)
8:5
于是把原公式中的2改成1.6,但显然当计算得到的空格数目不是整数的,输出距离还是不正确。
在这里插入图片描述
我又想到可以用看不到的中文字符(灵感来源于王者荣耀重复名)对原字符串进行填充,使其中的中文字符数是5的倍数。在查阅资料后,找到了全角空格(全角空格占据的宽度等于中文字符宽度)。
因此my_format函数可以改为:

def my_format(str, width, align):#定义函数接受三个参数:要输出的字符串(str)、总占用宽度(int)、对齐方式(str:l、r对应左右)
    sigle = 0
    double = 0
    longkong = 0
    sep=' '#定义占位符
    long_sep = ' '
    for i in str:#统计单字宽和1.6字宽的数目
        if len(i.encode('UTF-8')) == 1 or len(i.encode('UTF-8')) == 2:
            sigle += 1
        elif len(i.encode('UTF-8')) == 3:
            double += 1
    if double % 5 != 0:
        longkong = (5 - double % 5)
        double += longkong
    if align == 'l':
        return str + long_sep * longkong + int(width * 2 - sigle - double * 1.6) * sep
    elif align == 'r':
        return long_sep * longkong + int(width * 2 - sigle - double * 1.6) * sep + str

另外字符‘·’的encode编码是2个字符,但其字符宽度是一个英文字符,因此在遍历时单独列出。
最后输出结果,完美对齐:
在这里插入图片描述
另外,由于在一半的文本编辑器如记事本中,中文字符的宽度依然是英文字符的2倍,所以如果输出到文本文件中时,需要修改my_format中的参数。

完整代码

import requests
from bs4 import BeautifulSoup
import re
def getHTMLText(url):
    try:
        r = requests.get(url, timeout=100)
        r.raise_for_status()
        r.encoding = 'GBK'
        #r.encoding = 'UTF-8'
        return r.text
    except:
        return ""

def parsePage(ilt, html):
    try:
        soup = BeautifulSoup(html, 'html.parser')
        '''
        f = open("ceshi.txt", 'w', encoding="UTF-8")
        f.write(str(soup))
        f.close()
        '''
        names = soup.find_all('div', attrs = {'class': 'name'})
        writers = soup.find_all('div', attrs={'class': 'publisher_info'})
        price_ns = soup.find_all('span', 'price_n')
        price_rs = soup.find_all('span', 'price_r')
        for i in range(len(names)):
            publisher = writers[2*i].text.split('(')[0].split(' ')[0].split(',')[0].split(',')[0].replace('?','·')
            title = names[i].text.split('(')[0].split('(')[0].split(' ')[0].split('(')[0].replace('——','--')
            price_n = price_ns[i].string
            price_r = price_rs[i].string
            #print(price_n, price_r)
            ilt.append([publisher, title, price_n, price_r])
    except:
        print("")

def printGoodsList(ilt):
    print(my_format('序号', 8, 'l') + my_format('书名', 25, 'l') +\
          my_format('作者', 15, 'l') + my_format('当前价格', 8, 'l') + my_format('定价', 8, 'l'))
    count = 0

    for g in ilt:
        count = count + 1
        print(my_format(str(count), 8, 'l') + my_format(g[1], 25, 'l') + \
              my_format(g[0], 15, 'l') + my_format(g[2], 8, 'l') + my_format(g[3], 8, 'l'))

def my_format(str, width, align):#定义函数接受三个参数:要输出的字符串(str)、总占用宽度(int)、对齐方式(str:l、r对应左右)
    sigle = 0
    double = 0
    longkong = 0
    sep=' '#定义占位符
    long_sep = ' '
    for i in str:#统计单字宽和1.6字宽的数目
        if len(i.encode('UTF-8')) == 1 or len(i.encode('UTF-8')) == 2:
            sigle += 1
        elif len(i.encode('UTF-8')) == 3:
            double += 1
    if double % 5 != 0:
        longkong = (5 - double % 5)
        double += longkong
    if align == 'l':
        return str + long_sep * longkong + int(width * 2 - sigle - double * 1.6) * sep
    elif align == 'r':
        return long_sep * longkong + int(width * 2 - sigle - double * 1.6) * sep + str

def main():
    kind = '01.03.52.00.00.00'#排行榜书的类别,这个种类是影视小说
    time = '-year-2020-0-1'#2020年整年的排行榜
    depth = 10#页数
    start_url = 'http://bang.dangdang.com/books/bestsellers/' + kind + time + '-'
    infoList = []
    for i in range(depth):
        try:
            url = start_url + str(i+1)
            html = getHTMLText(url)
            parsePage(infoList, html)
        except:
            continue
    printGoodsList(infoList)

main()
  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

宫水二叶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值