决战高考,帮你秒变成语之王

又是一年高考季

每年的这时候我都要发一条朋友圈,要高考了好紧张,哈哈...
没开始做公众号时,在公司内网隔三差五发篇博文,感觉自己思维好活跃。可从上周五写第一篇公众号开始,说巧不巧的工作开始忙起来了。每天赶着最后一班车回家,吃完饭和孩子玩一会就快10点了。然后坐在电脑旁边稳定半天的情绪开始学习,等学的差不多开始搞公众号。这一周1天通宵5天3点后,感觉身体被掏空。但希望能坚持每天发一篇真的能给大家带来帮助的文章,那么今天发什么?如题...

成语之王

想想今天高考,第一门就是语文,所以考虑发一篇和语文有关的内容。
回忆我们那会儿考语文,第一道题肯定和成语、拼音的判断对错和修改有关。那就发这个吧!
百度了一下,找到一个haoshiwen.org的网站,对应的有一个chengyu.haoshiwen.org子域名。其中包含了很多的成语,点到每个成语中,又包含了成语、注音、释义、出处、示例、成语热度,很是不错。

网站分析

坦白说,这个网站真的比较简单,就是PHP(世界上最好的语言...)搭建的静态网站。
网站将成语通拼音首字母从A-Z进行了划分,然后每个字母一页一页的记录,网址模式如下:
http://chengyu.haoshiwen.org/list.php?t=A&page=2

5847426-566ee3de370d8e37.png
成语首页.png

每个成语的链接点进去就是成语详情,url类似如下:
http://chengyu.haoshiwen.org/view.php?id=40

5847426-e33a3d14440f8a84.png
成语详情.png

这样解析起来就很简单了...

  1. 我们首先创建双重for循环,character从A到Z、page 1--100,然后开始爬取网站
  2. 通过正则匹配所有href属性带有/view.php?id=数字的a标签进行网站拼接后的二次访问
  3. 进入访问页面,获取第一个table中的成语名、注音、释义、出处、示例、成语热度
  4. 成语热度的排名,进行字符串的切割后,转为int,已做成语热度排序
  5. 最终将数据进行存储
    ... 部分代码 ...
    def traverse_character(self):
        # page style: http://chengyu.haoshiwen.org/list.php?t=A&page=1
        url = self.url + '/list.php'
        for char in ascii_uppercase:
            for page_num in range(1, 100):
                params = {"t": char, "page": page_num}
                soup = self.get_response(url, params)
                links = soup.findAll("a", {"href": re.compile("/view.php\?id=\d")})
                if not links:
                    continue
                for link in links:
                    t = threading.Thread(target=self.idiom_index, args=(self.url + link["href"],))
                    t.start()
                    time.sleep(0.1)

    def idiom_index(self, url):
        sem.acquire()
        try:
            soup = self.get_response(url)
            tr = soup.find("table").findAll("tr")[:6]
            info = {td.findAll('td')[0].text: td.findAll('td')[1].text for td in tr}
            info['人气'] = int(info['人气'][:-1])
            sql = "insert into idiom (name,speak,meaning,source,example,hot) values " \
                  "(?,?,?,?,?,?)"

            self.db.insert(sql,list(info.values()))
        except Exception as error_info:
            print(error_info)
        finally:
            sem.release()
    ... 部分代码 ...
怎么存?

其实这种数据,用postgresql和mongodb亦或者是大众常用的Mysql来搞是比较好的。但其实这里有一个小打算,就是把数据全部拿到之后做一个其他的功能,为了能轻松复制,我选择了相对性能弱但是便携的SQLite。
那选择了数据库,用什么来管理数据库链接呢? DBUtils

安装pip install DBUtils
介绍:

DBUtils is a suite of tools providing solid, persistent and pooled connections to a database that can be used in all kinds of multi-threaded environments like Webware for Python or other web application servers. The suite supports DB-API 2 compliant database interfaces and the classic PyGreSQL interface.

简而言之,DBUtils是一套为数据库提供可靠,持久和池式连接的工具,可用于各种多线程环境。我们一般使用DBUtils.PooledDB来创建一批连接池进行并发处理。常用参数如下:

参数说明
creator使用链接数据库的模块(sqllite3、pymysql...)
maxconnections连接池允许的最大连接数,0和None表示不限制连接数
mincached初始化时,链接池中至少创建的空闲的链接,0表示不创建
maxcached链接池中最多闲置的链接,0和None不限制
blocking连接池中如果没有可用连接后,是否阻塞等待。True,等待;False,不等待然后报错
maxusage一个链接最多被重复使用的次数,None表示无限制
hostip
user用户名
password密码
database数据库名
charset字符集(Mysql用的比较多,SQLite没有)

因为之前都是拿DBUtils链接数据库的,这次默认就直接改成sqlite3,结果各种报错(SQLite数据库没有host、user、password、charset的选项)。
然后好不容易配置好了,封装上常用的方法...一跑程序挂了!
SQLite本身应对多个线程并发访问过程中的冲突问题,由一个线程创建并访问的sqlite的数据库,无法允许另外一个线程进行访问,找解决办法呗,最终找到通过设置check_same_thread=False,使SQLite支持多线程并发(但并发的效果很一般)。

# -*- coding: utf-8 -*-
# @Author   : 王翔
# @JianShu  : 清风Python
# @Date     : 2019/6/7 02:23
# @Software : PyCharm
# @version  :Python 3.6.8
# @File     : db_maker.py


import sqlite3
from DBUtils.PooledDB import PooledDB


class DB_Maker:
    def __init__(self):
        self.POOL = PooledDB(
            check_same_thread=False,
            creator=sqlite3,  # 使用链接数据库的模块
            maxconnections=10,  
            mincached=2,  
            maxcached=5,  
            blocking=True,  
            maxusage=None,  
            ping=0,
            database='database.db',
        )
        self.check_db()

    def check_db(self):
        sql = "SELECT name FROM sqlite_master where name=?"
        if not self.fetch_one(sql, ('idiom',)):
            self.create_table()

    def create_table(self):
        print("create table ...")
        sql = """create table idiom (
                        [id]            integer PRIMARY KEY autoincrement,
                        [name]         varchar (10),
                        [speak]      varchar (30),
                        [meaning]      varchar (100),
                        [source]      varchar (100),
                        [example]      varchar (100),
                        [hot]      int(10)
                    )"""
        self.fetch_one(sql)

    def db_conn(self):
        conn = self.POOL.connection()
        cursor = conn.cursor()
        return conn, cursor

    @staticmethod
    def db_close(conn, cursor):
        cursor.close()
        conn.close()

    def fetch_one(self, sql, args=None):
        conn, cursor = self.db_conn
        if not args:
            cursor.execute(sql)
        else:
            cursor.execute(sql, args)
        record = cursor.fetchone()
        self.db_close(conn, cursor)
        return record

    def fetch_all(self, sql, args):
        conn, cursor = self.db_conn
        cursor.execute(sql, args)
        record_list = cursor.fetchall()
        self.db_close(conn, cursor)
        return record_list

    def insert(self, sql, args):
        conn, cursor = self.db_conn
        row = cursor.execute(sql, args)
        conn.commit()
        self.db_close(conn, cursor)

程序引入threading多进程的方式,并发网页请求。主程序代码如下:

# -*- coding: utf-8 -*-
# @Author   : 王翔
# @JianShu  : 清风Python
# @Date     : 2019/6/7 02:52
# @Software : PyCharm
# @version  :Python 3.6.8
# @File     : IdiomCrawler.py

import requests
from bs4 import BeautifulSoup
import re
import threading
import time
from string import ascii_uppercase
from db_maker import DB_Maker


class IdiomCrawler:
    def __init__(self):
        self.url = "http://chengyu.haoshiwen.org"
        self.headers = {
            'Host': "chengyu.haoshiwen.org",
            'Connection': 'keep-alive',
            'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
            'user-agent': ('Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 '
                           '(KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36')
        }
        self.db=DB_Maker()

    def get_response(self, url, params=None):
        r = requests.get(url, headers=self.headers, params=params)
        r.encoding = 'utf-8'
        soup = BeautifulSoup(r.text, "lxml")
        return soup

    def traverse_character(self):
        # page style: http://chengyu.haoshiwen.org/list.php?t=A&page=1
        url = self.url + '/list.php'
        for char in ascii_uppercase:
            for page_num in range(1, 100):
                params = {"t": char, "page": page_num}
                soup = self.get_response(url, params)
                links = soup.findAll("a", {"href": re.compile("/view.php\?id=\d")})
                if not links:
                    continue
                for link in links:
                    t = threading.Thread(target=self.idiom_index, args=(self.url + link["href"],))
                    t.start()
                    time.sleep(0.1)

    def idiom_index(self, url):
        sem.acquire()
        try:
            soup = self.get_response(url)
            tr = soup.find("table").findAll("tr")[:6]
            info = {td.findAll('td')[0].text: td.findAll('td')[1].text for td in tr}
            info['人气'] = int(info['人气'][:-1])
            sql = "insert into idiom (name,speak,meaning,source,example,hot) values " \
                  "(?,?,?,?,?,?)"

            self.db.insert(sql,list(info.values()))
        except Exception as error_info:
            print(error_info)
        finally:
            sem.release()


if __name__ == '__main__':
    sem = threading.Semaphore(5)  # 设置线程阀值
    Crawler = IdiomCrawler()
    Crawler.traverse_character()
    while threading.active_count() != 1:
        pass  # print threading.active_count()
    else:
        print('### Selenium Jobs is over!!!###')

不爬不知道一爬吓一跳,中国有这么多的成语,看了几集Flask视频了,才终于搞完。
来看看我们的数据吧....

5847426-1cec45d35e3ee4cc.png
成语总数.png

检查下有没有重复数据,结果ok:
5847426-02e7a798604a5433.png
成语筛选.png

再来看看网站排名的成语热度吧:
5847426-3bfbfe9fb8e64f63.png
网站成语热度.png

这个热度就有点坑了,明显是访问次数么,因为这个 哀哀父母是第一个词,所以热搜最高。最重要的是我不知道这是啥意思,哈哈......

辛辛苦苦把三万个成语爬下来,就这么结束了貌似不太好,抽时间拿这些数据搞点事情。
如果大家想要这份成语数据库,关注我的公众号【清风Python】,回复成语,即可获取。
好了,今天的内容就到这里,如果觉得有帮助,记得点赞支持。欢迎大家关注我的公众号,获取更多Python相关的知识,并有整理好的各类福利数据供大家下载:

5847426-90a9ec02ff01c5d9.gif
清风Python

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值