又是一年高考季
每年的这时候我都要发一条朋友圈,要高考了好紧张,哈哈...
没开始做公众号时,在公司内网隔三差五发篇博文,感觉自己思维好活跃。可从上周五写第一篇公众号开始,说巧不巧的工作开始忙起来了。每天赶着最后一班车回家,吃完饭和孩子玩一会就快10点了。然后坐在电脑旁边稳定半天的情绪开始学习,等学的差不多开始搞公众号。这一周1天通宵5天3点后,感觉身体被掏空。但希望能坚持每天发一篇真的能给大家带来帮助的文章,那么今天发什么?如题...
成语之王
想想今天高考,第一门就是语文,所以考虑发一篇和语文有关的内容。
回忆我们那会儿考语文,第一道题肯定和成语、拼音的判断对错和修改有关。那就发这个吧!
百度了一下,找到一个haoshiwen.org的网站,对应的有一个chengyu.haoshiwen.org子域名。其中包含了很多的成语,点到每个成语中,又包含了成语、注音、释义、出处、示例、成语热度,很是不错。
网站分析
坦白说,这个网站真的比较简单,就是PHP(世界上最好的语言...)搭建的静态网站。
网站将成语通拼音首字母从A-Z进行了划分,然后每个字母一页一页的记录,网址模式如下:
http://chengyu.haoshiwen.org/list.php?t=A&page=2
每个成语的链接点进去就是成语详情,url类似如下:
http://chengyu.haoshiwen.org/view.php?id=40
这样解析起来就很简单了...
- 我们首先创建双重for循环,character从A到Z、page 1--100,然后开始爬取网站
- 通过正则匹配所有href属性带有/view.php?id=数字的a标签进行网站拼接后的二次访问
- 进入访问页面,获取第一个table中的成语名、注音、释义、出处、示例、成语热度
- 成语热度的排名,进行字符串的切割后,转为int,已做成语热度排序
- 最终将数据进行存储
... 部分代码 ...
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表示无限制 |
host | ip |
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视频了,才终于搞完。
来看看我们的数据吧....
检查下有没有重复数据,结果ok:
再来看看网站排名的成语热度吧:
这个热度就有点坑了,明显是访问次数么,因为这个 哀哀父母是第一个词,所以热搜最高。最重要的是我不知道这是啥意思,哈哈......
辛辛苦苦把三万个成语爬下来,就这么结束了貌似不太好,抽时间拿这些数据搞点事情。
如果大家想要这份成语数据库,关注我的公众号【清风Python】,回复成语
,即可获取。
好了,今天的内容就到这里,如果觉得有帮助,记得点赞支持。欢迎大家关注我的公众号,获取更多Python相关的知识,并有整理好的各类福利数据供大家下载: