本文章来记录下python结课时我的爬虫课设。
前面会写的稍微详细一些,完整代码的话会放在文章最后。
1、问题描述(功能要求):
目标网站:https://www.kingname.info/archives/。
目标内容:如下图所示内容,包括文章标题、发布时间、文章分类、文章链接、文章正文(HTML格式)。
(1)爬取列表页第1页所有的文章标题和文章详情;
(2)并保存爬取信息;
(3)截取与正文相关的源代码并保存;
看完问题描述之后,接下来就是分析和实践。
1.向目标网页发出请求
定义的head字典是用来装请求头信息的,你添加的键值对越多,目标网站就越认为你是真实的用户,但本次的目标网站反爬措施较为简单(好像就没有反爬措施),所以我在head字典里面就添加了基础的User-Agent这个键值对,这个键值对是用来描述你的操作系统、浏览器等信息。
在这里我还加入了异常处理,如果遇到异常的话,e.code会打印状态码,e.reason会打印出错原因。这个函数最后返回的对象是目标网页的全部源代码。
def gethtml(url):
head = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36 Edg/87.0.664.75"
}
request = urllib.request.Request(url, headers=head)
html = ""
try:
response = urllib.request.urlopen(request)
html = response.read().decode('utf-8')
except urllib.error.URLError as e:
if hasattr(e, 'code'): # 如果有错误信息,将e里的code打印出来
print(e.code)
if hasattr(e, "reason"): # 如果返回对象里面有reason这个对象,就看里面的reason
print(e.reason)
return html
2.获取目标网站博客的文章标题、文章发布时间和文章详情链接
虽然这个函数代码多点,但比较简单。首先定义三个列表datalist,datatime,datalink来分别存储文章标题,文章发布时间和文章链接。然后就是定义正则表达式,匹配需要的数据,再分别把三个目标内容存到指定的列表中。这里用了BeautifulSoup这个库,因为它处理数据比较方便。
最后需要注意一点的是,
data = re.findall(finddata, str(item))[0]和alink = re.findall(findalink, str(item))[0]
这两行最后都有个[0],这是因为我在匹配文章标题和文章链接这两个数据时用的是正则表达式,不像找文章发布时间那样直接用的是find()函数,re.findall()返回的是一个列表,所以要加上[0]来表示需要的是返回列表中的第一个元素。
def gettitle(url):
datalist = []
datatime = []
datalink = []
findalink = re.compile('')
for i in range(1, 15): # for循环遍历所有页面
finddata = re.compile('(.*)')
if i == 1:
findurl = url
else:
findurl = url + 'page/' + str(i) + '/'
html = gethtml(findurl)
soap = BeautifulSoup(html, 'html.parser') # 用html.parser来解析该html网页
for item in soap.find_all('span', itemprop="name"):
data = re.findall(finddata, str(item))[0]
datalist.append(data)
for item1 in soap.find_all('time', class_='post-time'):
a = str(item1).find('content') + 9
a1 = str(item1).find('"', a)
datatime.append(str(item1)[a:a1])
for item in soap.find_all('a', class_="post-title-link"):
alink = re.findall(findalink, str(item))[0]
datalink.append('https://www.kingname.info/' + alink) # 'https://www.kingname.info/' + alink才是一个文章完整的链接
return datalist, datatime, datalink
3.保存文章详情
保存文章详情的话,我是先用正则表达式获取所有文章的链接,(其实完全可以用gettitle()函数里的datalink[]列表,但因为我没有将其设置为全局部变量,所以就又用正则表达式匹配了一遍)将其放入alinklist[]列表中,再用for循环遍历列表,依次获得响应对象,再用css选择器直接匹配具体的文章内容,最后再将所有的文章内容都保存到一个markdown文件中。
def getarticle(url):
alinklist = []
html = gethtml(url)
findalink = re.compile('')
soap = BeautifulSoup(html, 'html.parser')
for item in soap.find_all('a', class_="post-title-link"):
alink = re.findall(findalink, str(item))[0]
alinklist.append(alink)
for i in alinklist:
articleurl = 'https://www.kingname.info/' + i
print(articleurl)
response = requests.get(articleurl)
html = response.text
sel = parsel.Selector(html)
content = sel.css('article').get()
text = tomd.Tomd(content).markdown
print(text)
with open('pc.md', mode='a', encoding='utf-8', )as f:
f.write(text)
f.close()
4.保存文章标题、文章发布时间和文章详情链接到数据库
将第二步获取到的信息保存到数据库,本代码中数据库用的是pycharm自带的SQLite数据库。
首先的话肯定要初始化数据库,之后再进行数据库的操作。dbpath是数据库名称,这个随便起就行。
saveData2DB()函数里的data[index] = '"' + data[index] + '"'这一行代码意思是把要存入数据库的每一个元素都给它加上双引号,这是因为本代码中向数据库存入的数据都是文本类型(也可以说是字符串类型),所以存入数据库的话要加双引号。
还有一个要注意的是saveData2DB()函数参数里的datalist2,它的数据类型是列表,而且这个列表还要是列表里面嵌列表,也就是这个样子:datalist2=[['文章1发布时间',’文章1标题‘,’文章1详情链接’],['文章2发布时间',’文章2标题‘,’文章2详情链接’],......]。
def init_db(dbpath): # 初始化数据库
sql = '''
create table RIN
(
id integer primary key autoincrement,
datetime text,
article_info text,
a_link text
)
'''
conn = sqlite3.connect(dbpath)
cursor = conn.cursor()
cursor.execute(sql)
conn.commit()
conn.close()
def saveData2DB(datalist2, dbpath): # 保存数据到数据库
init_db(dbpath)
conn = sqlite3.connect(dbpath) # 创建数据库连接对象
cur = conn.cursor() # 创建游标对象
for data in datalist2:
for index in range(len(data)):
data[index] = '"' + data[index] + '"'
sql = '''
insert into RIN(datetime, article_info, a_link)
values (%s)''' % ",".join(data)
cur.execute(sql) # 执行sql语句
conn.commit() # 提交事务
cur.close()
conn.close()
数据库操作成功的话,就是下面这样
5.进行网页截图
问题描述里面有个是截取与正文相关的源代码并保存,因为当时不太理解这个问题,所以就直接用phantomjs把文章详情的整个网页截图了。
这个是调用phantomjs的函数:
def getphoto(url):
cmd = 'phantomjs pageload.js "%s"' % url
subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()
这个是js代码:
var page = require('webpage').create(); //创建了一个webpage对象
var str = 'https://www.kingname.info/2021/05/20/use-scrapy-jsonrequest/'
function a(str){
page.viewportSize = { width: 1920, height: 1080 };
page.open(str, function (status) {
console.log("Status: " + status);
if (status === "success") { //判断响应状态
page.render('rin1.png'); //进行网页截图
}
phantom.exit();
});
}
a(str)
截图的话我是只截取了一张,因为phantomjs虽然好用,但运行速度还是比较慢的,截取的截图会默认存放在你代码文件的同级目录下。
总结:
总的来说这个课程设计还是有许多有待改进的地方的,其中最主要的问题就是运行速度慢,我觉得用多线程编程的话应该会缩短一些时间(当时想到了但是没有去实现)。最后,如果大家在看这篇文章时发现了什么问题,希望能及时指出来。
完整代码如下:
pythonKCSJ.py:
import re
import urllib.request
import urllib.error
import urllib.parse
import parsel
import requests
import tomd
from bs4 import BeautifulSoup
import sqlite3
import subprocess
def main():
baseurl = 'https://www.kingname.info/archives/'
gethtml(baseurl)
datalist, datatime, datalink = gettitle(baseurl)
datalist2 = []
for i in range(0, len(datalist)):
datalist1 = []
datalist1.append(datatime[i])
datalist1.append(datalist[i])
datalist1.append(datalink[i])
datalist2.append(datalist1)
getarticle(baseurl)
getphoto(baseurl)
dbpath = 'ISHTAR.db'
saveData2DB(datalist2, dbpath)
def gethtml(url):
head = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36 Edg/87.0.664.75"
}
request = urllib.request.Request(url, headers=head)
html = ""
try:
response = urllib.request.urlopen(request)
html = response.read().decode('utf-8')
except urllib.error.URLError as e:
if hasattr(e, 'code'): # 如果有错误信息,将e里的code打印出来
print(e.code)
if hasattr(e, "reason"): # 如果返回对象里面有reason这个对象,就看里面的reason
print(e.reason)
return html
def gettitle(url):
datalist = []
datatime = []
datalink = []
findalink = re.compile('')
for i in range(1, 15): # for循环遍历所有页面
finddata = re.compile('(.*)')
if i == 1:
findurl = url
else:
findurl = url + 'page/' + str(i) + '/'
html = gethtml(findurl)
soap = BeautifulSoup(html, 'html.parser') # 用html.parser来解析该html网页
for item in soap.find_all('span', itemprop="name"):
data = re.findall(finddata, str(item))[0]
datalist.append(data)
for item1 in soap.find_all('time', class_='post-time'):
a = str(item1).find('content') + 9
a1 = str(item1).find('"', a)
datatime.append(str(item1)[a:a1])
for item in soap.find_all('a', class_="post-title-link"):
alink = re.findall(findalink, str(item))[0]
datalink.append('https://www.kingname.info/' + alink) # 'https://www.kingname.info/' + alink才是一个文章完整的链接
return datalist, datatime, datalink
def getarticle(url):
alinklist = []
html = gethtml(url)
findalink = re.compile('')
soap = BeautifulSoup(html, 'html.parser')
for item in soap.find_all('a', class_="post-title-link"):
alink = re.findall(findalink, str(item))[0]
alinklist.append(alink)
for i in alinklist:
articleurl = 'https://www.kingname.info/' + i
print(articleurl)
response = requests.get(articleurl)
html = response.text
sel = parsel.Selector(html)
content = sel.css('article').get()
text = tomd.Tomd(content).markdown
print(text)
with open('pc.md', mode='a', encoding='utf-8', )as f:
f.write(text)
f.close()
def getphoto(url):
cmd = 'phantomjs pageload.js "%s"' % url
subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()
def init_db(dbpath): # 初始化数据库
sql = '''
create table RIN
(
id integer primary key autoincrement,
datetime text,
article_info text,
a_link text
)
'''
conn = sqlite3.connect(dbpath)
cursor = conn.cursor()
cursor.execute(sql)
conn.commit()
conn.close()
def saveData2DB(datalist2, dbpath): # 保存数据到数据库
init_db(dbpath)
conn = sqlite3.connect(dbpath) # 创建数据库连接对象
cur = conn.cursor() # 创建游标对象
for data in datalist2:
for index in range(len(data)):
data[index] = '"' + data[index] + '"'
sql = '''
insert into RIN(datetime, article_info, a_link)
values (%s)''' % ",".join(data)
cur.execute(sql) # 执行sql语句
conn.commit() # 提交事务
cur.close()
conn.close()
if __name__ == '__main__':
main()
pageload.js:
var page = require('webpage').create();
var str = 'https://www.kingname.info/2021/05/20/use-scrapy-jsonrequest/'
function a(str){
page.viewportSize = { width: 1920, height: 1080 };
page.open(str, function (status) {
console.log("Status: " + status);
if (status === "success") {
page.render('rin1.png');
}
phantom.exit();
});
}
a(str)