本文仅为学习python过程的一个笔记,其中还有一些bug! 还请各位大佬赐教

有些专业的说法还不是很熟悉,欢迎各位大佬帮忙指出

本人时一个新晋奶爸,而立之年突然想转业,想学习python

先介绍一个大致思路

本文抓取的电影下载网站是我读书期间最近经常下载电影的链接为www.hao6v.tv

分析网页,没有动态加载内容,所以应该算是比较简单。

网页我大致分成两类,一类是类似目录和电影索引页面,暂时称为moviepage(英语不好,也不知道取啥名,后期在代码中也是这样分类的),一类是电影详情及下载页面,暂时称为movieinfo

首先抓取主页内的所有符合条件的url和标题,去重后放到moviepage数据表中,后期利用递归在从moviepage中筛选出未被抓取的页面链接进行爬取,在抓取到电影详情页面时,只获取电影名,介绍,下载链接,并保存到movieinfo中。

以上就是大致的一个思路

目前我自己了解到的几个问题:

1、虽然采用了多线程,而且我也想了办法去限制线程数,但好像线程数量并没有被限制

2、采用了多线程,但因为调用MySQL写入数据,所有采用了几个mutex避免出现数据错乱,但好像非常影响效率。

3、为了方便,同时为了避免多线程获取游标(cur)的问题,所以每次调用MySQL时都会新建连接,在执行完后会关闭MySQL,但这导致一个bug,有时候MySQL会被提前关闭

4、最大的bug,在具体写的时候,我准备了两套写入数据方案,一个是写入MySQL中,一个是直接写到文档中。这个目前还有较多问题未有调试

5、潜在的bug,因为目标网页的子页面非常多,而且很多内容会有重叠,虽然能筛查重复元素,但抓取到后期的效率肯定会越来越低

因为在实际操作过程中需要反复调用数据库操作,所以把数据库操作单独写了一个文件

import os.path
import threading
import time
import json
import requests
from bs4 import BeautifulSoup
import pysql
import re
import random# 现在的问题就是再使用mysql时,因为我每次调用后默认会关闭,再多线程时就会出现数据库提前被关闭,需要避免这种情况
# 有以下两种思路
# 1、每次调用mysql(pysql)时,给一个新的变量
# 2、直接取消默认关闭,但可能出现链接用户过多,无法链接的情况# 0、从数据库moviepage表中获取url,如果数据表中没有则从输入的网页链接开始
# 1、登入网页、使用BeautifulSoup解析网页
# 2、分析网页信息,如果是普通页面,执行线路①,如果是电影详情页面执行线路②# 线路①# 1、获取网页所有超文本连接 获取信息包含(超链接文本,超链接url)# 2、比对数据库现有超文本连接,去重后将新的超文本链接存入数据库moviepage表# 线路②# 1、获取网页电影名称,电影简介,电影下载磁力链接等链接# 2、将获取的信息存入数据库movieinfo表# 将所有步骤封装到一个类中,需要获取的信息有首次爬取的网页链接
class GetMovie(object):"""本类中可以调用请求头headers,调用代理proxies,调用数据库mysql推荐使用数据库保存数据,在修改数据时效率高"""def __init__(self,url:str=None,headers_list:list=None,proxies_list:list=None):"""本类中可以调用请求头headers,调用代理proxies,调用数据库mysql如果不传入请求头,则使用默认请求头,不传入代理,则默认不使用代理代理列表格式为['代理ip:代理port','代理ip:代理port'...]如需调用mysql写入数据,则在调用main()之前调用mysql()默认是返回文本数据,调用mysql则di"""self.url = urlself.headers_list= headers_listself.proxies_list = proxies_listself.headers = {'user-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) ''AppleWebKit/537.36 (KHTML, like Gecko) Chrom''e/86.0.4240.111 Safari/537.36 Edg/86.0.622.61',}self.proxies = Noneself.db = Noneself.db_statu = False  # 记录数据库开启情况,默认未开启self.url_list = []  # 后期实际爬取网页是从这个列表中获取urlself.html_num = 0  # 用来记录爬取网页的数量self.movie_num = 0  # 用来记录爬取的电影链接数量self.deep_num = 0  # 记录爬取深度self.mutex_write_page = threading.Lock()  # 写入page数据的锁self.mutex_write_info = threading.Lock()  # 写入info数据的锁self.mutex_html_num = threading.Lock()  # 记录爬取页面数量的锁self.mutex_movie_num = threading.Lock()  # 记录爬取电影数量的锁self.mutex_write_is_read = threading.Lock()  # 修改状态的锁pass# 处理请求头 self.headers将从请求头列表中获得一个随机的请求头def __getHeaders(self):"""责处理请求头,从请求头字典里面随机调出一个请求头,赋值给实例变量self.headers"""if self.headers_list:l = len(self.headers_list)i = random.randrange(l)self.headers= self.headers_list[i]pass# 处理代理  self.proxies将从代理列表中获得一个随机的代理def __getProxies(self):"""处理代理ip和端口,并赋值给实例变量self.proxies"""if self.proxies_list:self.proxies = {}l = len(self.proxies_list)while True:i = random.randrange(l)self.proxies['http'] = self.proxies_list[i]# 利用的http://icanhazip.com/返回的IP进行校验,如返回的是代理池的IP,说明代理有效,否则实际代理无效try:res = requests.get(url="http://www.hao6v.tv", timeout=2, headers=self.headers,proxies=self.proxies)if res.status_code == 200:with open("proxies_new.txt",'a+',encoding='utf8') as f:f.write(f'{self.proxies}\n')breakelse:print(f"1{self.proxies}代理IP无效!")self.proxies_list.remove(i)l -=1continueexcept:print(f"2{self.proxies}代理IP无效!")continuepass# 处理数据库登入信息def mysql(self, host: str = '127.0.0.1', user:str = 'root',password: str = None,database: str = None,port:int = 0):"""默认的数据库地址为本地数据库127.0.0.1,用户名为root,默认的端口为0"""self.db = pysql.PySql(host=host,user=user,password=password,database=database,port=port)self.db_statu = Truepass# 从数据库或文档中获取爬取的链接列表def __urls(self):"""默认使用的数据库工作表为moviepage moviepage表有id,name,link,is_read,is_del信息,name为超链接文本,link为超链接is_read为判断本条链接是否为已经爬取,默认为int(0),is_del为是否删除,默认为int(0)默认的moviepage文档是多行文本,每行数据是一个字典,字典包含id name link,is_read,is_del信息结果时创建一个爬取时所需要的url列表执行后三个结果:1、如果没有数据表或文档,则返回输入的url2、如果有数据表或文档,则查找符合条件的link①如果没有符合则表示查询完成,返回空的url列表②如果有则采用多线程继续爬取网页数据,返回url列表"""# 1、判断是否启用数据库,启用则调用数据里面的信息获取url,否则从文本。如果文本或者数据没有信息,则为默认链接if self.db_statu:sql_s = """select id,link,is_read,is_del from moviepage where is_read = '0' and is_del = '0';"""db1 = self.dbselect_data = db1.select(sql_s)# 如果查询失败,返回None,则创建表并新增第一条数据,同时更新self.url_list的信息,把查询的链接放进去if select_data is None:print("--正在创建表--")sql_c = """create table moviepage(id int(11) auto_increment primary key,name varchar(100) not null,link varchar(500) not null,is_read enum('0','1') default '0',is_del enum('0','1') default '0');"""db1.create(sql_c)print("首次查询,查询链接为:%s" % self.url)sql_i = f"""INSERT INTO moviepage(name,link) VALUES('首页','{self.url}')"""db1.insert(sql_i)self.url_list.append(self.url)# 如果查询结果为空值,则返回一个空的self.url_list,后期用来判断查询完成elif select_data == tuple():self.url_list = []# 如果不为空,则将返回的元组进行遍历,获得的新元组通过切片获取url,把url添加到self.url_list中,方便再次调用查询else:for i in select_data:self.url_list.append(i[1])else:if os.path.exists(r"moviepage.txt"):f = open(r'moviepage.txt','r')f_data = f.read().split('\n')for j in f_data[:-1]:  # 必须要切片,否则最后一个空值也会导入,再loads就会出错i = json.loads(j.replace("\'","\""))i = dict(i)if i['is_read'] == '0' and i['is_del'] == '0':self.url_list.append(i['link'])f.close()else:f = open(r'moviepage.txt','w+',encoding='utf8')f.write("{'id':%d,'name':'首页','link':'%s','is_read':'0','is_del':'0'}\n" %(self.html_num,self.url))f.close()self.url_list.append(self.url)pass# 定义获取网页的函数def __run(self,link):"""本函数需要link是"""# 1、登入网页、使用BeautifulSoup解析网页self.mutex_html_num.acquire()self.html_num += 1self.mutex_html_num.release()count_html = requests.get(url=link, headers=self.headers, timeout=10, proxies=self.proxies)count_html.encoding = count_html.apparent_encodingif count_html.status_code == 200:print(f"{link}信息采集成功!")# 调用一个函数,用来标记已爬取的网页,将爬取后的网页链接状态标记为1,即is_read = '1'self.__write_is_read(link)else:print(f"{link}请求失败!错误码{count_html.status_code}")count = BeautifulSoup(count_html.text, 'lxml')# 2、分析网页信息,如果是电影详情页面执行线路①,如果是普通页面,执行线路②if "下载地址" in count.text:self.__saveMovieInfo(count)else:self.__saveMoviePage(count)# 更改链接爬取状态def __write_is_read(self,link):# 如果是数据库if self.db_statu:# 写入锁self.mutex_write_is_read.acquire()# 修改sql_u = f"""update moviepage set is_read = "1" where link = '{link}';"""db2 = self.dbdb2.update(sql_u)self.mutex_write_is_read.release()# 如果是文档else:# 写入锁self.mutex_write_is_read.acquire()# 获取文档全部内容f = open(r'moviepage.txt','w+',encoding='utf8')f_list = f.read().split("\n")f.write('')  # 清空文件f.close()# 遍历所所有行for e in f_list[:-1]:e = json.loads(e.replace("\'","\""))echo = dict(e)# 如果是link所在行if echo['link'] == link:# 修改对应的is_readecho['is_read'] = '1'else:echo = echo# 写入新的moviepaga.txt文件中n_f = open(r'moviepage.txt','a+',encoding='utf8')n_f.write(str(echo)+'\n')  # 写入新的文件,并在结尾换行n_f.close()self.mutex_write_is_read.release()pass# 保存电影链接和信息def __saveMovieInfo(self,count):"""movieinfo 有id name info link is_del"""# 0、先解析数据内容,将内容筛选获得name,info,link# 获取标题,通过soup的find功能查找第一个标签为‘title'name = count.find('title').string.split(',')[0]  # 标题# 详情介绍需要先定位到“内容介绍:",然后查找后面所有内容,再从下载content_regexp = re.compile(u'内容介绍')content = count.find(text=content_regexp)# 查找在内容介绍后面所有的内容,转成文字next_content = content.find_all_next('p')  # find_all和parents获取的内容都是以类似列表返回info = ''for i in next_content:info += (i.text+'\n')s = info.find(u'【下载地址】')info = info[:s].strip('\n')  # 内容介绍download_content = count.find('table')downloads = download_content.find_all('a', href=True)downloads_dict = {}for i in downloads:downloads_dict[i.text] = i['href']link = str(downloads_dict)  # 链接地址# 即将写入数据,加锁并将记录加1self.mutex_movie_num.acquire()self.movie_num += 1self.mutex_movie_num.release()# 1、判断数据库是否运行if self.db_statu:self.mutex_write_info.acquire()# 判断数据是否已经存在,已经存在则修改,否则新增sql_s = f"""select * from movieinfo where name = '{name}'"""db3 = self.dbresult = db3.select(sql_s)if result == None:print("--原表没有,正在新增--")sql_c = """create table movieinfo(id mediumint unsigned primary key auto_increment,name varchar(50) not null unique, info text,link varchar(800) not null unique,is_del enum("0","1") not null default '0');"""db3.create(sql_c)elif result == tuple():sql_i = f"""insert into movieinfo(name,info,link) values ('{name}','{info}','{link}');"""db3.insert(sql_i)else:  # 已存在,升级数据sql_u = f"""update movieinfo set link = '{link}' where name = '{name}'"""db3.update(sql_u)self.mutex_write_info.release()# 保存为文档else:self.mutex_write_info.acquire()with open(r'movieinfo.txt','a+',encoding='utf8') as f:f.write(f"{'id':{self.movie_num},'name':'{name}','info':'{info}','link':'{link}','is_del':'0'}\n")self.mutex_write_info.release()pass# 保存普通页面的  后续爬取的链接都从这里产生,修改爬取的链接得修改这里def __saveMoviePage(self,count):# 0、解析网页内容,获取满足条件链接名称和urlself.mutex_write_page.acquire()body = count.find('body')url_list = body.find_all('a', href=True)link_name_dict = {}pattern = '^https://'for urls in url_list:name = urls.texturl = urls['href']  # 这里可能会爬取到不属于self.url为主页的链接,在下面会进行排除if re.match(pattern,url):link = urlp = f'^{self.url}'if re.match(p,link):link_name_dict[link] = nameelse:link = self.url+urlif len(re.findall(":",link)) == 1:link_name_dict[link] = nameif self.db_statu:sql_s = """select name,link from moviepage;"""db4 = self.dbs_r = db4.select(sql_s)if s_r is None:print("----savemoviepage----")elif s_r == tuple():print("查询结果为空")else:for name,link in s_r:if link in link_name_dict.keys():  # 同一个url可能时不同名字,所以这里不能使用url反向指定namelink_name_dict.pop(link)for link_add,name_add in link_name_dict.items():self.html_num+=1sql_i = f"""insert into moviepage(name,link) values ('{name_add}','{link_add}');"""db4.insert(sql_i)else:# 默认的moviepage文档是多行文本,每行数据是一个字典,字典包含id name link, is_read, is_del信息f = open(r'moviepage.txt','r')page_list = f.read().split("\n")f.close()for p in page_list[:-1]:p = json.loads(p.replace("\'","\""))page = dict(p)if page['link'] in link_name_dict.keys():link_name_dict.pop(page['link'])wf = open(r'moviepage.txt','a+',encoding='utf8')for link,name in link_name_dict.items():self.html_num +=1wf.write(f"{'id':{self.html_num},'name':{name},'link':{link},'is_read':'0',is_del='0'}\n")wf.close()self.mutex_write_page.release()passdef write_in_mysql(self):passdef write_in_txt(self):pass# 主函数,递归调规自己,直至爬取完成def main(self,n:int=5):"""n表示需要调用的线程数,默认为5"""if self.deep_num > 1:print("查询停止,共爬取了%d个页面,共收集了%d个电影下载链接,爬取深度为:%d轮!" % (self.html_num, self.movie_num,self.deep_num))else:self.__getHeaders()  # 获取请求头self.__getProxies()  # 获取代理self.__urls()  # 处理网页链接列表l = len(self.url_list)# 利用递归批量爬取,设置一个条件,满足条件自动跳出递归if l != 0:r = l // n# 调用__run()会运行爬取网页并保存数据for i in range(r + 1):if i * n + n < len(self.url_list):for link in self.url_list[i * n:i * n + n]:threading.Thread(target=self.__run, args=(link,)).start()while True:  # 必须设置主线程等待,否则限制线程数就相当于没用time.sleep(0.5)length = len(threading.enumerate())print("当前子线程数:",length-1)if length <= 1:breakelse:for link in self.url_list[i * n:l]:threading.Thread(target=self.__run, args=(link,)).start()while True:time.sleep(0.5)length = len(threading.enumerate())print("当前子线程数:",length-1)if length <=1:breakself.deep_num +=1self.main(n=n)else:print("查询已完成,总共爬取了%d个页面,共收集了%d个电影下载链接,爬取深度为:%d轮!" % (self.html_num, self.movie_num,self.deep_num))if __name__ == "__main__":proxy_list = []with open(r'proxies.txt',"r") as p:p = p.read().split("\n")for i in p:proxy_list.append(i.strip('\t'))headers_list = []with open(r'Headers.txt','r') as h:h = h.read().split("\n")for j in h:dic = {}dic['user-Agent'] = j.strip(",").strip("'")headers_list.append(dic)f = GetMovie(url='https://www.hao6v.tv',headers_list=headers_list)f.mysql(host='127.0.0.1',user='root',password='xx',database='xx')f.main(n=1)

数据库操作代码,这个代码应该没啥问题,功能比较简单,主要是在上面需要重复调用,所以单独写了一个进行调用

import pymysql# 设置5个功能
# 1、新增table
# 2、查询表内容
# 3、修改表内容
# 4、增加表内容
# 5、删除表内容class PySql():def __init__(self,host: str = '127.0.0.1', user:str = 'root',password: str = None,database: str = None,port:int = 0):self.host = hostself.user = userself.password = passwordself.database = databaseself.port = portself.db = Noneself.c_num = 0self.i_num = 0self.u_num = 0self.d_num = 0self.s_num = 0passdef __connect(self):try:self.db=pymysql.connect(host=self.host, user=self.user,password=self.password, database=self.database, port=self.port)except Exception as ret:print("数据库链接失败",ret)self.db = None# 通用操作,获取游标,执行数据库语句,commitdef __generalOp(self,sql): cur = self.db.cursor()cur.execute(sql)self.db.commit()def create(self,sql_c):""" sql-c格式为:create table xxx(...);"""self.__connect()try:self.__generalOp(sql_c)self.c_num +=1print("创建table成功%d" % self.c_num)except Exception as ret:print(f"创建{sql_c}失败,请检查语句或查看ret",'\nret:',ret)finally:self.db.close()pass# 查询不需要commit,但一般需要获取查询内容,所以设置了一个returndef select(self,sql_s):""" sql_s格式为:select * from xxx where xxx and/or xxx;"""self.__connect()try:cur = self.db.cursor()cur.execute(sql_s)result = cur.fetchall()self.s_num +=1print("查询成功%d" % self.s_num)return resultexcept Exception as ret:print(f"查询失败{sql_s},请检查语句或查看ret", '\nret:', ret)return Nonefinally:self.db.close()passdef update(self,sql_u):""" sql_u格式为:update xxx set xx = x where yy = y...;"""self.__connect()try:self.__generalOp(sql_u)self.u_num+=1print("修改成功%d" % self.u_num)except Exception as ret:print(f"修改{sql_u}失败,请检查语句或查看ret", '\nret:', ret)finally:self.db.close()passdef insert(self,sql_i):"""sql_i格式为:insert into xxx(xx,xx,xx) values (x,x,x),(x,x,x)..."""self.__connect()try:self.__generalOp(sql_i)self.i_num +=1print("新增成功%d" % self.i_num)except Exception as ret:print(f"新增{sql_i}失败,请检查语句或查看ret", '\nret:', ret)finally:self.db.close()passdef delete(self,sql_d):"""sql_d格式为:delete from xxx where yy..."""self.__connect()try:self.__generalOp(sql_d)self.d_num +=1print("删除成功%d" % self.d_num)except Exception as ret:print(f"删除{sql_d}失败,请检查语句或查看ret", '\nret:', ret)finally:self.db.close()pass

经过自己测试,目前这个代码在子线程只有1的时候,可以正确调用mysql写入数据,但线程变多后就会出现数据库被提前关闭的错误。

直接用文本写入数据的问题目前还没有修复

Python 多线程、利用request使用代理、利用递归深度抓取电影网页的内容并将电影的介绍和下载链接保存到mysql中相关推荐

  1. Python爬虫:爬取知乎上的视频,并把下载链接保存到md文件中

    Python爬虫:爬取知乎上的视频,并把下载链接保存到md文件中 1.需要的Python模块 主要是requests模块,用于得到的网页的数据 安装命令为:pip install requests 2 ...

  2. Python爬虫实战(1)——百度贴吧抓取帖子并保存内容和图片

    最近在网上看了很多的爬虫脚本,写的参差不齐,但是其中有很多写的非常的优秀,代码质量很高,规范性也很好,很具有代表性,非常值得我们去学习!~ 写好一个python爬虫需要有以下几个必备条件: 1.足够好 ...

  3. python画桃花_Python小白如何使用爬虫自动抓取《三生三世十里桃花》豆瓣电影短评...

    1.准备工作 python是一门相对于其他语言来说肥肠自由的语言,从它只能用空白符作为强制缩进符就能够感受到它与众不同,爱用不用的独特气质,像这样一位潇洒任性的公子自然免不得要提前做一些准备才能驾驭. ...

  4. python爬虫获取下一页_Python Scrapy 自动抓取下一页内容

    最近在学下Scrapy,抓取下一页的时候遇到了问题 import scrapy from crawlAll.items import CrawlallItem class ToutiaoEssayJo ...

  5. 一篇文章让你轻松学会python爬取的数据保存到MySQL中,有案例哦

    文章目录 pymysql 基本使用 八个步骤以及案例分析 一.导入pymysql模块 二.获取到database的链接对象 三.创建数据表的方法 四.获取执行sql语句的光标对象 五.定义要执行的sq ...

  6. 利用新浪API实现数据的抓取\微博数据爬取\微博爬虫 1

    PS:(本人长期出售超大量微博数据.旅游网站评论数据,并提供各种指定数据爬取服务,Message to YuboonaZhang@Yahoo.com.由于微博接口更新后限制增大,这个代码已经不能用来爬 ...

  7. GAN还有这种操作!谷歌大脑和X实验室利用模拟条件和域适应提高机器抓取效率(附论文)

    来源:机器人圈 作者:Coogle Brain,Google X 概要:相信大家都知道,通过检测和采集带有注释的视觉抓取数据集来训练现代机器学习算法可以说是非常耗时.昂贵的. 相信大家都知道,通过检测 ...

  8. 利用wget 抓取 网站网页 包括css背景图片

    利用wget 抓取 网站网页 包括css背景图片 wget是一款非常优秀的http/ftp下载工具,它功能强大,而且几乎所有的unix系统上都有.不过用它来dump比较现代的网站会有一个问题:不支持c ...

  9. 利用新浪API实现数据的抓取\微博数据爬取\微博爬虫

    PS:(本人长期出售超大量微博数据.旅游网站评论数据,并提供各种指定数据爬取服务,Message to YuboonaZhang@Yahoo.com.由于微博接口更新后限制增大,这个代码已经不能用来爬 ...

最新文章

  1. mysql persistent_MySQL关于InnoDB的几个错误
  2. 当退出python时是否释放全部内存_Python面试题:高级特性考察
  3. AX宏Macros运算
  4. [蓝桥杯][算法提高VIP]分分钟的碎碎念(dfs)
  5. poj 2696 A Mysterious Function
  6. cesium billboard 设置距离控制可见度
  7. Windows中查找文件被何进程使用
  8. 【收藏】史上最全推荐系统传统算法合集
  9. 响应式下的雪碧图解决方案 - 活用background-size / background-position
  10. 数据库中多对多的关系设计
  11. springweb 初步理解
  12. 一个百分号%引起的事故
  13. 样条曲线_概念设计:如何控制相关样条曲线几何图形?
  14. java安装后打开jar文件_java环境变量配置好后双击jar文件无法运行的解决办法
  15. a6账套管理显示无法连接服务器,航天A6基础版简单建账实施流程
  16. java导出excel 图片_请教java导出多张图片到Excel问题!
  17. JQuery.validate验证表单后Ajax异步提交
  18. Unity实现将图片上传到服务器功能
  19. discuz教程 毫无基础常识的站长搭建HTTPS。图文并茂
  20. C++程序设计:考研路茫茫——早起看书

热门文章

  1. 服务器固态硬盘的优缺点是什么
  2. 《译 SFML Essentials 英文版》—— 《第一章》 SFML 入门
  3. 图像分割、图像超分辨率简介
  4. FANUC机器人的EE接口使用方法
  5. Sqlmap 用户手册 使用教程
  6. 抖音商家发货超时处罚规则,特殊情况及申诉要求是什么丨国仁猫哥
  7. Java操作ZIp文件
  8. kaldi中的深度神经网络
  9. 在angular中,我有一个路由'/sdfsd/sss/ss',实现在一函数,判断路由配置对象中是否存在该路由...
  10. Arduino框架下通过TFT_eSPI库驱动ESP32+合宙1.54“ 电子墨水屏(e-paper)显示