안녕하세요 여러분 잔슈씨 입니다!
멀리서 친구가 찾아오면 참 좋지 않을까요? 친구들이 우리와 함께 놀러 오는 것은 매우 행복한 일이므로 집주인이되어 친구들을 데리고 놀 수 있도록 최선을 다해야합니다! 그래서 질문은 언제 가장 좋은 시간과 어디로 가야 하는지, 그리고 가장 재미있는 장소는 어디인가 하는 것입니다.
오늘은 스레드 풀을 활용해 관광지 정보를 크롤링하고, 같은 여행의 데이터를 리뷰하고, 워드클라우드와 데이터 시각화를 만드는 방법을 차근차근 가르쳐드리겠습니다! ! ! 다양한 도시의 관광명소 정보를 알려드립니다.
데이터 크롤링을 시작하기 전에 먼저 스레드에 대해 이해해 봅시다.
프로세스: 프로세스는 데이터 수집에 대한 코드의 실행 활동이며 시스템의 리소스 할당 및 예약의 기본 단위입니다.
스레드: 경량 프로세스이자 프로그램 실행의 최소 단위이자 프로세스의 실행 경로입니다.
프로세스에는 하나 이상의 스레드가 있으며 프로세스의 여러 스레드는 프로세스의 리소스를 공유합니다.
다중 스레드를 생성하기 전에 먼저 아래 그림과 같이 스레드 수명주기에 대해 알아봅시다.
그림에서 볼 수 있듯이 스레드는 5가지 상태로 나눌 수 있습니다. - New , 준비 및 실행 중, 차단, 종료.
먼저 새 스레드를 생성하고 스레드를 시작합니다. 스레드가 준비 상태에 들어간 후에는 CPU 리소스를 얻은 후에만 실행 상태로 들어갑니다. 스레드는 CPU 리소스를 잃거나 발생할 수 있습니다. 절전 또는 IO 작업(읽기, 쓰기 등) 시 스레드는 준비 상태 또는 차단 상태로 들어가며 절전, IO 작업이 끝나거나 CPU 리소스가 부족할 때까지 실행 상태로 들어가지 않습니다. 실행 후 종료 상태로 들어갑니다.
참고: 새로운 스레드 시스템을 생성하려면 리소스 할당이 필요하고, 스레드 시스템을 종료하려면 리소스 재활용이 필요합니다. 그렇다면 스레드 생성/종료에 따른 시스템 오버헤드를 어떻게 줄일 수 있을까요? 이때 스레드 풀을 생성하여 스레드를 재사용할 수 있습니다. 시스템 오버헤드를 줄일 수 있다는 것입니다.
스레드 풀을 만들기 전에 먼저 멀티스레드 생성 방법을 알아볼까요?
멀티 스레드 만들기는 다음 네 단계로 나눌 수 있습니다.
시연의 편의를 위해 블로그 파크의 웹 페이지를 크롤러 기능으로 사용합니다. 구체적인 코드는 다음과 같습니다. import requests
urls=[
f'https://www.cnblogs.com/#p{page}'
for page in range(1,50)
]
def get_parse(url):
response=requests.get(url)
print(url,len(response.text))
이전 단계에서는 크롤러 함수를 생성했으며 다음으로 스레드를 생성합니다. 구체적인 코드는 다음과 같습니다. import threading
#多线程
def multi_thread():
threads=[]
for url in urls:
threads.append(
threading.Thread(target=get_parse,args=(url,))
)
스레드가 생성되었으며 다음 스레드가 시작됩니다. 스레드를 시작하는 것은 매우 간단합니다. 구체적인 코드는 다음과 같습니다.for thread in threads:
thread.start()
스레드를 시작한 후 스레드가 끝날 때까지 기다립니다. 구체적인 코드는 다음과 같습니다. for thread in threads:
thread.join()
멀티스레딩이 생성되었습니다. 다음으로, 구체적인 코드는 다음과 같습니다.
if __name__ == '__main__': t1=time.time() multi_thread() t2=time.time() print(t2-t1)
실행 결과는 다음과 같습니다.
멀티스레딩은 50개의 블로그를 크롤링합니다. 공원 웹 페이지는 1초 이상만 소요되며 다중 스레드 네트워크 요청의 URL은 무작위입니다.단일 스레드의 실행 시간을 테스트해 보겠습니다. 구체적인 코드는 다음과 같습니다.
if __name__ == '__main__': t1=time.time() for i in urls: get_parse(i) t2=time.time() print(t2-t1)
실행 결과는 아래 그림과 같습니다.
블로그 파크 웹 50개를 크롤링하는 데 9초 이상 걸렸습니다. 단일 스레드가 있는 페이지. 네트워크 요청을 보내는 URL은 순차적입니다.위에서 말했듯이 새로운 스레드 시스템을 만들려면 리소스 할당이 필요하고 스레드 시스템을 종료하려면 리소스 재활용이 필요합니다. 시스템 오버헤드를 줄이기 위해 스레드 풀을 만들 수 있습니다.
一个线程池由两部分组成,如下图所示:
当任务队列里有任务时,线程池的线程会从任务队列中取出任务并执行,执行完任务后,线程会执行下一个任务,直到没有任务执行后,线程会回到线程池中等待任务。
使用线程池可以处理突发性大量请求或需要大量线程完成任务(处理时间较短的任务)。
好了,了解了线程池原理后,我们开始创建线程池。
Python提供了ThreadPoolExecutor类来创建线程池,其语法如下所示:
ThreadPoolExecutor(max_workers=None, thread_name_prefix='', initializer=None, initargs=())
其中:
注意:在启动 max_workers 个工作线程之前也会重用空闲的工作线程。
在ThreadPoolExecutor类中提供了map()和submit()函数来插入任务队列。其中:
map()函数
map()语法格式为:
map(调用方法,参数队列)
具体示例如下所示:
import requestsimport concurrent.futuresimport timeurls=[f'https://www.cnblogs.com/#p{page}'for page in range(1,50)]def get_parse(url):response=requests.get(url)return response.textdef map_pool():with concurrent.futures.ThreadPoolExecutor(max_workers=20) as pool:htmls=pool.map(get_parse,urls)htmls=list(zip(urls,htmls))for url,html in htmls:print(url,len(html))if __name__ == '__main__':t1=time.time()map_pool()t2=time.time()print(t2-t1)
首先我们导入requests网络请求库、concurrent.futures模块,把所有的URL放在urls列表中,然后自定义get_parse()方法来返回网络请求返回的数据,再自定义map_pool()方法来创建代理池,其中代理池的最大max_workers为20,调用map()方法把网络请求任务放在任务队列中,在把返回的数据和URL合并为元组,并放在htmls列表中。
运行结果如下图所示:
可以发现map()函数返回的结果和传入的参数顺序是对应的。
注意:当我们直接在自定义方法get_parse()中打印结果时,打印结果是乱序的。
submit()函数
submit()函数语法格式如下:
submit(调用方法,参数)
具体示例如下:
def submit_pool():with concurrent.futures.ThreadPoolExecutor(max_workers=20)as pool:futuress=[pool.submit(get_parse,url)for url in urls]futures=zip(urls,futuress)for url,future in futures:print(url,len(future.result()))
运行结果如下图所示:
注意:submit()函数输出结果需需要调用result()方法。
好了,线程知识就学到这里了,接下来开始我们的爬虫。
首先我们进入同程旅行的景点网页并打开开发者工具,如下图所示:
经过寻找,我们发现各个景点的基础信息(详情页URL、景点id等)都存放在下图的URL链接中,
其URL链接为:
https://www.ly.com/scenery/NewSearchList.aspx?&action=getlist&page=2&kw=&pid=6&cid=80&cyid=0&sort=&isnow=0&spType=&lbtypes=&IsNJL=0&classify=0&grade=&dctrack=1%CB%871629537670551030%CB%8720%CB%873%CB%872557287248299209%CB%870&iid=0.6901326566387387
经过增删改查操作,我们可以把该URL简化为:
https://www.ly.com/scenery/NewSearchList.aspx?&action=getlist&page=1&pid=6&cid=80&cyid=0&isnow=0&IsNJL=0
其中page为我们翻页的重要参数。
打开该URL链接,如下图所示:
通过上面的URL链接,我们可以获取到很多景点的基础信息,随机打开一个景点的详情网页并打开开发者模式,经过查找,评论数据存放在如下图的URL链接中,
其URL链接如下所示:
https://www.ly.com/scenery/AjaxHelper/DianPingAjax.aspx?action=GetDianPingList&sid=12851&page=1&pageSize=10&labId=1&sort=0&iid=0.48901069375088
其中:action、labId、iid、sort为常量,sid是景点的id,page控制翻页,pageSize是每页获取的数据量。
在上上步中,我们知道景点id的存放位置,那么构造评论数据的URL就很简单了。
这次我们爬虫步骤是:
首先我们先获取景点的名字、id、价格、特色、地点和等级,主要代码如下所示:
def get_parse(url):response=requests.get(url,headers=headers)Xpath=parsel.Selector(response.text)data=Xpath.xpath('/html/body/div')for i in data:Scenery_data={'title':i.xpath('./div/div[1]/div[1]/dl/dt/a/text()').extract_first(),'sid':i.xpath('//div[@]/div/@sid').extract_first(),'Grade':i.xpath('./div/div[1]/div[1]/dl/dd[1]/span/text()').extract_first(), 'Detailed_address':i.xpath('./div/div[1]/div[1]/dl/dd[2]/p/text()').extract_first().replace('地址:',''),'characteristic':i.xpath('./div/div[1]/div[1]/dl/dd[3]/p/text()').extract_first(),'price':i.xpath('./div/div[1]/div[2]/div[1]/span/b/text()').extract_first(),'place':i.xpath('./div/div[1]/div[1]/dl/dd[2]/p/text()').extract_first().replace('地址:','')[6:8]}
首先自定义方法get_parse()来发送网络请求后使用parsel.Selector()方法来解析响应的文本数据,然后通过xpath来获取数据。
获取景点基本信息后,接下来通过景点基本信息中的sid来构造评论信息的URL链接,主要代码如下所示:
def get_data(Scenery_data):for i in range(1,3):link = f'https://www.ly.com/scenery/AjaxHelper/DianPingAjax.aspx?action=GetDianPingList&sid={Scenery_data["sid"]}&page={i}&pageSize=100&labId=1&sort=0&iid=0.20105777381446832'response=requests.get(link,headers=headers)Json=response.json()commtent_detailed=Json.get('dpList')# 有评论数据if commtent_detailed!=None:for i in commtent_detailed:Comment_information={'dptitle':Scenery_data['title'],'dpContent':i.get('dpContent'),'dpDate':i.get('dpDate')[5:7],'lineAccess':i.get('lineAccess')}#没有评论数据elif commtent_detailed==None:Comment_information={'dptitle':Scenery_data['title'],'dpContent':'没有评论','dpDate':'没有评论','lineAccess':'没有评论'}
首先自定义方法get_data()并传入刚才获取的景点基础信息数据,然后通过景点基础信息的sid来构造评论数据的URL链接,当在构造评论数据的URL时,需要设置pageSize和page这两个变量来获取多条评论和进行翻页,构造URL链接后就发送网络请求。
这里需要注意的是:有些景点是没有评论,所以我们需要通过if语句来进行设置。
这次我们把数据存放在MySQL数据库中,由于数据比较多,所以我们把数据分为两种数据表,一种是景点基础信息表,一种是景点评论数据表,主要代码如下所示:
#创建数据库def create_db():db=pymysql.connect(host=host,user=user,passwd=passwd,port=port)cursor=db.cursor()sql='create database if not exists commtent default character set utf8'cursor.execute(sql)db.close()create_table()#创建景点信息数据表def create_table():db=pymysql.connect(host=host,user=user,passwd=passwd,port=port,db='commtent')cursor=db.cursor()sql = 'create table if not exists Scenic_spot_data (title varchar(255) not null, link varchar(255) not null,Grade varchar(255) not null, Detailed_address varchar(255) not null, characteristic varchar(255)not null, price int not null, place varchar(255) not null)'cursor.execute(sql)db.close()
首先我们调用pymysql.connect()方法来连接数据库,通过.cursor()获取游标,再通过.execute()方法执行单条的sql语句,执行成功后返回受影响的行数,然后关闭数据库连接,最后调用自定义方法create_table()来创建景点信息数据表。
这里我们只给出了创建景点信息数据表的代码,因为创建数据表只是sql这条语句稍微有点不同,其他都一样,大家可以参考这代码来创建各个景点评论数据表。
创建好数据库和数据表后,接下来就要保存数据了,主要代码如下所示:
首先我们调用pymysql.connect()方法来连接数据库,通过.cursor()获取游标,再通过.execute()方法执行单条的sql语句,执行成功后返回受影响的行数,使用了try-except语句,当保存的数据不成功,就调用rollback()方法,撤消当前事务中所做的所有更改,并释放此连接对象当前使用的任何数据库锁。
#保存景点数据到景点数据表中def saving_scenery_data(srr):db = pymysql.connect(host=host, user=user, password=passwd, port=port, db='commtent')cursor = db.cursor()sql = 'insert into Scenic_spot_data(title, link, Grade, Detailed_address, characteristic,price,place) values(%s,%s,%s,%s,%s,%s,%s)'try:cursor.execute(sql, srr)db.commit()except:db.rollback()db.close()
注意:srr是传入的景点信息数据。
好了,单线程爬虫已经写好了,接下来将创建一个函数来创建我们的线程池,使单线程爬虫变为多线程,主要代码如下所示:
urls = [f'https://www.ly.com/scenery/NewSearchList.aspx?&action=getlist&page={i}&pid=6&cid=80&cyid=0&isnow=0&IsNJL=0'for i in range(1, 6)]def multi_thread():with concurrent.futures.ThreadPoolExecutor(max_workers=8)as pool:h=pool.map(get_parse,urls)if __name__ == '__main__':create_db()multi_thread()
创建线程池的代码很简单就一个with语句和调用map()方法
运行结果如下图所示:
好了,数据已经获取到了,接下来将进行数据分析。
首先我们来分析一下各个景点那个月份游玩的人数最多,这样我们就不用担心去游玩的时机不对了。
我们发现10月、2月、1月去广州长隆飞鸟乐园游玩的人数占总体比例最多。分析完月份后,我们来看看评论情况如何:
可以发现去好评占了绝大部分,可以说:去长隆飞鸟乐园玩耍,去了都说好。看了评论情况,评论内容有什么:
好了,获取旅游景点信息及评论并做词云、数据可视化就讲到这里了。
위 내용은 Python은 관광 명소 정보 및 리뷰를 획득하고 워드 클라우드 및 데이터 시각화를 생성합니다.의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!