进入穷游网,找到行程助手,点击进去
然后找到搜索,随便搜索一下,发现地址栏的url支持中文关键字
查看url规律
以日本为例,url需要变动的是页码数和关键字
这个爬虫程序,用进程爬取地区,用协程抓取地区的每一个页面
使用xpath匹配页数,使用正则匹配数字,获取最大页数
qiongyou_1 .py
import multiprocessing import re import requests import gevent from gevent import monkey monkey.patch_all() from lxml import etree from time import sleep # 定义一个函数请求每隔地区的首页 def fetch_areas(url,headers,area):
area_html = requests.get(url=url%(1,area),headers=headers).text a_tree = etree.HTML(area_html) # 使用xpath匹配页数 lastPage = a_tree.xpath('//a[@class="ui_page_item"]/text()')[-1] print(lastPage) #使用正则匹配数字,获取最大页数 totalPages = int(re.findall(pattern=r'\d+',string=lastPage)[0]) print(totalPages) # 用协程抓取每一个页面 # 定义一个列表,用于管理和当前进程中的所有协程 g_list = [] for page in range(1,totalPages+1): page_url = url%(page,area) # 创建一个协程 g = gevent.spawn(fetch_pages_per_area,page_url,headers) g_list.append(g) gevent.joinall(g_list) # 定义一个函数,用于请求每个地区的页面信息 def fetch_pages_per_area(url,headers): pass if __name__ == '__main__': #定义一个列表,规定要抓取的那些地区 areas =['中国','欧洲','泰国','韩国','日本','新加坡','南美洲'] url = 'http://plan.qyer.com/search_0_0_0_0_0_0_%d/?keyword=%s' # 请求头 headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.131 Safari/537.36'} #对于计算密集型的程序,一般用进程,线程,协程都行;但对于I/O密集型的程序,一般不用进程 # 用进程完成每个地区的请求 p_list = [] for area in areas: # 创建进程 p = multiprocessing.Process(target=fetch_areas,args=(url,headers,area)) p.start() p_list.append(p) for p in p_list: p.join()
运行一下,可以获取
(monkey模块可能会报错,但不影响数据的获取)
使用xpath匹配信息
然而,不是所有都是那么顺利,如果就按造xpath匹配的内容获取,会有出错的地方,有些不匹配
比如匹配标题,有一些没有标题,或者url不匹配,打印出来看一下
# 定义一个函数,用于请求每个地区的页面信息 def fetch_pages_per_area(url, headers): # 打印异常 try: print('当前正在请求:',url) res = requests.get(url=url,headers=headers) sleep(1) #解析 page_tree = etree.HTML(res.text) travels = page_tree.xpath('//div[@class="items"]') for travel in travels: item ={
} item['title'] = travel.xpath('.//dd/text()')[0] print(item) except Exception as e: print('url:%s出异常了'%url) print(e)
还是有一些这种情况的
于是,这些出错的,把那一项就设为空,也是一个方法
qiongyou_2 .py
import multiprocessing import re import requests import gevent from gevent import monkey monkey.patch_all() from lxml import etree from time import sleep # 定义一个函数请求每隔地区的首页 def fetch_areas(url, headers, area):
area_html = requests.get(url=url % (1, area), headers=headers).text a_tree = etree.HTML(area_html) # 使用xpath匹配页数 lastPage = a_tree.xpath('//a[@class="ui_page_item"]/text()')[-1] # 使用正则匹配数字,获取最大页数 totalPages = int(re.findall(pattern=r'\d+', string=lastPage)[0]) # print(totalPages) # 用协程抓取每一个页面 # 定义一个列表,用于管理和当前进程中的所有协程 g_list = [] for page in range(1, totalPages + 1): page_url = url % (page, area) # 创建一个协程 g = gevent.spawn(fetch_pages_per_area, page_url, headers) g_list.append(g) gevent.joinall(g_list) # 定义一个函数,用于请求每隔地区的页面信息 def fetch_pages_per_area(url, headers): print('当前正在请求:', url) res = requests.get(url=url, headers=headers) sleep(2) # 解析 page_tree = etree.HTML(res.text) travels = page_tree.xpath('//div[@class="items"]') for travel in travels: item = {
} item["title"] = travel.xpath(".//dd/text()")[0] if travel.xpath(".//dd/text()") else " " item["start_time"] = travel.xpath(".//dt/text()")[0] if travel.xpath(".//dt/text()") else " " item["cycle"] = travel.xpath(".//div[@class='day']//text()")[0] if travel.xpath( ".//div[@class='day']//text()") else " " tag = travel.xpath(".//div[starts-with(@class,'tag')]//text()") if len(tag) != 0: item["tag"] = " ".join(tag) else: item["tag"] = "自由行" item["plan"] = travel.xpath(".//div[@class='plan']/p/text()")[0] if travel.xpath( ".//div[@class='plan']/p/text()") else " " item["next_url"] = "http:" + travel.xpath(".//a[@class='link']/@href")[0] if travel.xpath( ".//a[@class='link']/@href") else " " print(item) if __name__ == '__main__': # 定义一个列表,规定要抓取的那些地区 areas = ['中国', '欧洲', '泰国', '韩国', '日本', '新加坡', '南美洲'] url = 'http://plan.qyer.com/search_0_0_0_0_0_0_%d/?keyword=%s' # 请求头 headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.131 Safari/537.36'} # 对于计算密集型的程序,一般用进程,线程,协程都行;但对于I/O密集型的程序,一般不用进程 # 用进程完成每个地区的请求 p_list = [] for area in areas: # 创建进程 p = multiprocessing.Process(target=fetch_areas, args=(url, headers, area)) p.start() p_list.append(p) for p in p_list: p.join()
换汤不换药,最后就是数据的存储了
这里偷下懒,直接存到redis数据库中,就不存csv文件了,其实也就是多加两行代码的事
qiongyou_3 .py
import multiprocessing import re import requests import redis import gevent from gevent import monkey monkey.patch_all() from lxml import etree from time import sleep # 定义一个函数请求每隔地区的首页 def fetch_areas(url, headers, area):
area_html = requests.get(url=url % (1, area), headers=headers).text a_tree = etree.HTML(area_html) # 使用xpath匹配页数 lastPage = a_tree.xpath('//a[@class="ui_page_item"]/text()')[-1] # 使用正则匹配数字,获取最大页数 totalPages = int(re.findall(pattern=r'\d+', string=lastPage)[0]) # print(totalPages) # 用协程抓取每一个页面 # 定义一个列表,用于管理和当前进程中的所有协程 g_list = [] for page in range(1, totalPages + 1): page_url = url % (page, area) # 创建一个协程 g = gevent.spawn(fetch_pages_per_area, page_url, headers) g_list.append(g) gevent.joinall(g_list) # 定义一个函数,用于请求每隔地区的页面信息 def fetch_pages_per_area(url, headers): print('当前正在请求:', url) res = requests.get(url=url, headers=headers) sleep(2) # 解析 page_tree = etree.HTML(res.text) travels = page_tree.xpath('//div[@class="items"]') for travel in travels: item = {
} item["title"] = travel.xpath(".//dd/text()")[0] if travel.xpath(".//dd/text()") else " " item["start_time"] = travel.xpath(".//dt/text()")[0] if travel.xpath(".//dt/text()") else " " item["cycle"] = travel.xpath(".//div[@class='day']//text()")[0] if travel.xpath( ".//div[@class='day']//text()") else " " tag = travel.xpath(".//div[starts-with(@class,'tag')]//text()") if len(tag) != 0: item["tag"] = " ".join(tag) else: item["tag"] = "自由行" item["plan"] = travel.xpath(".//div[@class='plan']/p/text()")[0] if travel.xpath( ".//div[@class='plan']/p/text()") else " " item["next_url"] = "http:" + travel.xpath(".//a[@class='link']/@href")[0] if travel.xpath( ".//a[@class='link']/@href") else " " print(item) rds = redis.StrictRedis(host='127.0.0.1',port=6379,db=9) rds.lpush('plans',item) if __name__ == '__main__': # 定义一个列表,规定要抓取的那些地区 areas = ['中国', '欧洲', '泰国', '韩国', '日本', '新加坡', '南美洲'] url = 'http://plan.qyer.com/search_0_0_0_0_0_0_%d/?keyword=%s' # 请求头 headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.131 Safari/537.36'} # 对于计算密集型的程序,一般用进程,线程,协程都行;但对于I/O密集型的程序,一般不用进程 # 用进程完成每个地区的请求 p_list = [] for area in areas: # 创建进程 p = multiprocessing.Process(target=fetch_areas, args=(url, headers, area)) p.start() p_list.append(p) for p in p_list: p.join()
连csv都不存了,是不是过分?
这里有一个问题,如果爬多了,会遇到反爬,就涉及到ip代理了