1、分析网页url规律
进入穷游网,找到行程助手,点击进去
然后找到搜索,随便搜索一下,发现地址栏的url支持中文关键字
查看url规律
以日本为例,url需要变动的是页码数和关键字
这个爬虫程序,用进程爬取地区,用协程抓取地区的每一个页面
2、定义函数请求每个地区的页数
使用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模块可能会报错,但不影响数据的获取)
3、定义函数请求每个地区的页面信息
使用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()
4、数据的存储
换汤不换药,最后就是数据的存储了
这里偷下懒,直接存到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代理了