网络爬虫(python项目)
一.request+正则表达式爬取猫眼电影
1.什么是request
Requests是用python语言基于urllib编写的,采用的是Apache2 Licensed开源协议的HTTP库
如果你看过上篇文章关于urllib库的使用,你会发现,其实urllib还是非常不方便的,而Requests它会比urllib更加方便,可以节约我们大量的工作。(用了requests之后,你基本都不愿意用urllib了)一句话,requests是python实现的最简单易用的HTTP库,建议爬虫使用requests库。
默认安装好python之后,是没有安装requests模块的,需要单独通过pip安装
import requests
response = requests.get("https://www.baidu.com")
print(type(response))
print(response.status_code)
print(type(response.text))
print(response.text)
print(response.cookies)
print(response.content)
print(response.content.decode("utf-8"))
##我们可以看出response使用起来确实非常方便,这里有个问题需要注意一下:
很多情况下的网站如果直接response.text会出现乱码的问题,所以这个使用response.content
这样返回的数据格式其实是二进制格式,然后通过decode()转换为utf-8,这样就解决了通过response.text直接返回显示乱码的问题.
请求发出后,Requests 会基于 HTTP 头部对响应的编码作出有根据的推测。当你访问 response.text 之时,Requests 会使用其推测的文本编码。你可以找出 Requests 使用了什么编码,并且能够使用 response.encoding 属性来改变它.如:
response =requests.get("http://www.baidu.com")
response.encoding="utf-8"
print(response.text)
不管是通过response.content.decode("utf-8)的方式还是通过response.encoding="utf-8"的方式都可以避免乱码的问题发生
二..利用Ajax分析使用代理处理反爬抓取今日头条街拍美图.
有些网页直接请求得的HTML代码并没有我们在浏览器看到的内容,因为一些信息是通过Ajax加载并且经过js渲染生成的。
网络库:request
解析库:BeautifulSoup 正则表达式
存储库:MongoDB (用到pymongo库)
1、分析网站源码。
网站是局部动态变化,offset变化加载内容,变化范围为0,20,40···
首先要获取索引页代码数据,定义索引页,由于是ajax请求,offset根据索引变化0、20、40···,用requests获取网页代码,urlcode将字典转换为url请求参数,然后异常处理,根据url_code状态码判断请求是否成功,返回文本格式,最后定义main函数调用。可根据可变参数’offset’和’keyworld’参数调用函数,改为def get_page-index(offset,keyworld)
from urllib.parse import urlencode
import requests
from requests.exceptions import RequestException
def get_page_index():
data = {
'offset': 0,
'format': 'json',
'keyword': '街拍',
'autoload': 'true',
'count': '20',
'cur_tab': 3
}
url = 'https://www.toutiao.com/search_content/?'+urlencode(data)
try:
response = requests.get(url)
if response.status_code == 200:
return response.text
return None
except RequestException:
print("请求索引页错误")
return None
def main():
html=get_page_index()
print(html)
if __name__ == '__main__':
main()
2、解析索引页,因为html返回的是json格式的字符串对象,json.loads加载json格式文件。判断data[‘data’]键是否存在,若存在,返回’article_url’的值
def parse_page_index(html):
data = json.loads(html)
if data and 'data' in data.keys():
for item in data.get('data'):
yield item.get('article_url')
然后在main函数中调用解析页的url
for url in parse_page_index(html):
print(url)
3、获取详情页代码
ef get_page_detail(url):
try:
response = requests.get(url)
if response.status_code == 200:
return response.text
return None
except RequestException:
print("请求详情页错误",url)
return None
4、解析详情页数据。
分析详情页源代码
查看详情页的网站源代码,发现图片集的url都在gallery键的值中。
首先,获取每个图片集的标题title,用select选择title标签下的文本
def parse_page_detail(html):
soup = BeautifulSoup(html,'lxml')
title = soup.select('title')[0].get_text()
print(title)
在main函数中判断html是否正确,返回结果
main():
html = get_page_index(0,'街拍')
for url in parse_page_index(html):
html = get_page_detail(url)
if html:
parse_page_detail(html)
获取每个图片集中的图片信息,所有图片信息都在gallery键的值中,通过re.comlile正则表达式解析,然后用search得到结果,因为此时得到的结果中信息不正确,有很多多余的反斜杠’\’,于是利用replace去掉斜杠。
获取到的结果为
image_pattern = re.compile('gallery: JSON.parse[(]"(.*?)"[)],\n',re.S)
result = re.search(image_pattern,html)
if result:
result = result.group(1).replace('\\','')
print(result)
结果是json字符串的格式,需要用loads解析,提取其中的每张照片的url,最后返回的是图集的标题、链接和每张图片的url
data=json.loads(result)
if data and 'sub_images' in data.keys():
sub_images=data.get('sub_images')
images_url=[item['url'] for item in sub_images]
return {
'title':title,
'url':url,
'images_url':images_url
}
输出的结果为:
此时,所有的信息已经提取完毕,开始存储数据
5、把数据存储到mongodb数据库中,首先在同一目录下,建立配置文件config.py,
MONGO_URL='localhost' #链接地址
MONGO_DB='toutiao' #数据库
MONGO_TABLE='toutiao' #数据集即表
通过from config import *调用该文件
#声明mongodb数据库对象
client=pymongo.MongoClient(MONGO_URL)
db=client[MONGO_DB]
然后定义函数存储到数据库中,并判断如果存储成功输出相应信息
def save_to_mongo(result):
if db[MONGO_TABLE].insert(result):
print("存储到mongodb成功",result)
return True
return False
6、接下来是根据图片链接下载图片
首先定义一个函数,利用pathlib库根据传入的目录名创建一个文件目录
def create_dir(name):
#根据传入的目录名创建一个目录,这里用到了 python3.4 引入的 pathlib 。
directory = Path(name)
if not directory.exists():
directory.mkdir()
return directory
然后定义下载图片函数,要求返回的是content,是二进制文件
def download_image(save_dir,url):
print("正在下载:",url)
try:
response = requests.get(url)
if response.status_code == 200:
#调用存储图片函数,返回二进制
save_image(save_dir,response.content)
return None
except RequestException:
print("请求图片出错",url)
return None
定义存储图片函数
def save_image(save_dir,content):
'''把文件保存到本地,文件有三部分内容(路径)/(文件名).(后缀)
用format构造字符串(项目路径,文件名,格式),md5文件名可以避免重复'''
#os.getcwd()程序同目录
#file_path='{0}/{1}.{2}'.format(os.getcwd(),md5(content).hexdigest(),'jpg')
file_path = '{0}/{1}.{2}'.format(save_dir, md5(content).hexdigest(), 'jpg')
#如果文件不存在,开始存入
if not os.path.exists(file_path):
with open(file_path,'wb') as f:
f.write(content)
f.close()
在parse_page_detail函数中,调用download_image
root_dir = create_dir('D:\spider\jiepai') # 保存图片的根目录
download_dir = create_dir(root_dir / title) # 根据每组图片的title标题名创建目录
for image in images:
download_image(download_dir, image) #下载所有的图片
三.代理处理反爬抓取微信公众号文章,将结构化数据存储到MongoDB.
搜狗(http://weixin.sogou.com/)已经为我们做了一层微信文章的爬取,通过它我们可以获取一些微信文章的列表以及微信公众号的一些信息,但是它有很多反爬虫的措施,可以检测到你的IP异常,然后把你封掉。本文采用代理的方法处理反爬来抓取微信文章。
(1)目标站点分析
打开搜狗微信,输入要查找的内容,比如我们输入“风景”,就会出现微信文章的列表,向下翻动我们可以发现每页有10条内容,在最下方可以进行翻页。需要注意的是,未登陆时最多可以查看10页内容,登陆之后就可以查看100页的内容(也就是说,做爬虫的时候可以使用cookie爬取到100页内容):
从网页的url可以看出这是一个get请求,只保留主要的请求参数,把url简化为:
其中,“query”代表搜索的关键词,“type”代表搜索结果的类型,“type=1”表示搜索结果是微信公众号,“type=2”表示搜索结果是微信文章,“page”也就是当前页数。
现在网页是能正常访问的,但当我们点击翻页比较频繁的话,就会出现访问出错,输入验证码之后才能再次正常访问:
对网页请求进行分析,可以发现正常请求时状态码都是200,出现访问出错时状态码变成了302:
状态码302又代表什么呢?百度一下:
HTTP状态码302表示临时性重定向,该状态码表示请求的资源已被分配了新的URI,希望用户(本次)能使用新的URI访问。
也就是说,我们原来的请求被跳转到了一个新的页面,这个新的页面就是访问出错,需要输入验证码的页面:
根据以上分析,我们可以根据请求返回的状态码判断IP是否被封,如果状态码是200说明可以正常访问,如果状态码是302,则说明IP已经被封。
接着我们再分析一下后续的页面。在列表里打开一条微信文章,我们可以获取到文章的标题、公众号的一些信息,以及文章内容。文章内容包括文字和一些图片,在这里我们只爬取文章中的文字部分。
(2)流程框架
1.抓取索引页内容
利用requests请求目标站点,得到索引网页HTML代码,返回结果。
2.代理设置
如果遇到302状态码,则说明IP被封,切换代理重试。
3.分析详情页内容
请求详情页,分析得到标题、正文等内容。
4.将数据保存到数据库
将结构化数据保存至MongoDB。
(3)爬虫代码
# weixin_article.py
import re
import requests
from urllib.parse import urlencode
from pyquery import PyQuery as pq
from requests.exceptions import ConnectionError
from weixin_article_config import *
import pymongo
from fake_useragent import UserAgent, FakeUserAgentError
import time
client = pymongo.MongoClient(MONGO_URL)
db = client[MONGO_DB]
proxy = None # 全局代理
def get_proxy():
try:
response = requests.get('http://127.0.0.1:5000/get')
if response.status_code == 200:
return response.text
return None
except ConnectionError:
return None
def get_html(url, count=1):
print('Crawling', url)
print('Trying Count', count)
global proxy # 引用全局变量
if count >= MAX_COUNT:
print('Tried Too Many Counts')
return None
try:
ua = UserAgent()
except FakeUserAgentError:
pass
headers = {
'Cookie': 'IPLOC=CN3203; SUID=4761C3782E08990A000000005B496B2A; SUV=1531538220705353; ABTEST=0|1531538246|v1; weixinIndexVisited=1; JSESSIONID=aaa5HdENmLh-idG2g6isw; PHPSESSID=ctkdrtb5sai55cpcd99uglsit4; SUIR=6D964C6E1B1E6F17ABEAE3C31C02C20B; ppinf=5|1533879269|1535088869|dHJ1c3Q6MToxfGNsaWVudGlkOjQ6MjAxN3x1bmlxbmFtZTo1OnN1Z2FyfGNydDoxMDoxNTMzODc5MjY5fHJlZm5pY2s6NTpzdWdhcnx1c2VyaWQ6NDQ6bzl0Mmx1QXg2cUNnY3dESUlCVkQ4REQzejdmVUB3ZWl4aW4uc29odS5jb218; pprdig=qmTYp6UdjuqBQsh41S2iPuC6aVGbF8-gC-oD4_JnXCEABwuE8dKqwUYrBA6ShYaZPxoNW11zcC9vnHQXr57mKAkCYl_j7HUAwAwPPkB8Hw8Iv1IBDQKe3oFFlmig9sp8N_H9VHyF9G-o03WmzDLDoZyiZ-3MPvM-olyDPQ4j_gY; sgid=31-36487535-AVttIibUibd0Gctt8SOaDTEqo; sct=4; wP_h=effa146bd88671d4ec8690f70eefe1957e085595; ppmdig=1533917694000000a1a24383d97adaba971d77d02f75fa59; SNUID=9028BCC8F9FC8BEEF42ED05AF94BDD9E; seccodeRight=success; successCount=1|Fri, 10 Aug 2018 16:20:22 GMT',
'Host': 'weixin.sogou.com',
'Referer': 'http://weixin.sogou.com',
'Upgrade-Insecure-Requests': '1',
'User-Agent': ua.random
}
try:
if proxy:
proxies = {'http': proxy}
response = requests.get(url, allow_redirects=False, headers=headers, proxies=proxies) # timeout=10
else:
response = requests.get(url, allow_redirects=False, headers=headers) # timeout=10
if response.status_code == 200:
return response.text
if response.status_code == 302:
# Need Proxy
print('302')
proxy = get_proxy()
if proxy:
print('Using Proxy', proxy)
return get_html(url)
else:
print('Get Proxy Failed')
return None
else:
print('Error Status Code', response.status_code)
return None
except ConnectionError as e:
print('Error Occurred', e.args)
proxy = get_proxy()
count += 1
return get_html(url, count)
def get_index(keyword, page):
data = {
'query': keyword,
'type': '2',
'page': page
}
url = 'http://weixin.sogou.com/weixin?' + urlencode(data)
return get_html(url)
def parse_index(html):
doc = pq(html)
items = doc('.news-box .news-list li .txt-box h3 a').items()
for item in items:
yield item.attr('href')
def get_detail(url):
try:
response = requests.get(url)
if response.status_code == 200:
return response.text
return None
except ConnectionError:
return None
def parse_detail(html, url):
doc = pq(html)
title = doc('.rich_media_title').text()
content = "".join(doc('.rich_media_content').text().split())
nickname = doc('.profile_nickname').text()
wechat = doc('#js_profile_qrcode > div > p:nth-child(3) > span').text()
date_pattern = re.compile('var publish_time = \"(.*?)\"', re.S)
date_search = re.search(date_pattern, html)
if date_search:
date = date_search.group(1)
else:
date = None
return {
'url': url,
'title': title,
'date': date,
'nickname': nickname,
'wechat': wechat,
'content': content
}
def save_to_mongo(data):
if db['articles'].update({'title': data['title']}, {'$set': data}, True):
print('Save to Mongo', data['title'])
return True
else:
print('Save to Mongo Failed', data['title'])
return False
def main():
for page in range(1, 101):
html = get_index(KEYWOED, page)
time.sleep(1) # 减小IP被封的风险
if html:
article_urls = parse_index(html)
for article_url in article_urls:
if article_url:
article_html = get_detail(article_url)
if article_html:
article_data = parse_detail(article_html, article_url)
if article_data:
save_to_mongo(article_data)
if __name__ == '__main__':
main()
# config.py
MONGO_URL = 'localhost'
MONGO_DB = 'weixin'
MONGO_TABLE = 'articles'
KEYWOED = '风景' # 关键词
MAX_COUNT = 5 # 最大请求次数
此外,需要注意的是,程序使用的代理是从自己维护的代理池中取用的,而代理池是从网上获取的一些公共IP,这些IP一般是不太稳定的,也有可能是很多人都在使用的,所以爬取效果可能不是很好,被禁的概率比较高。当然,你也可以使用自己购买的一些代理,只需要改写“get_proxy”方法就ok了。
最后,还需要注意Cookies的一些设置。从下面的图中我们可以看出,cookies是有它自己的过期时间的,如果在抓取过程中发现无法获取后面的内容,有可能是cookies已经过期了,这时候我们重新登录一下,然后替换掉原来的Cookies就好了。
---------------------
原文:https://blog.csdn.net/polyhedronx/article/details/81561456?utm_source=copy
四.使用Selenium模拟浏览器抓取淘宝商品美食信息.
1.相关工具介绍
- Selenium是一个自动化测试工具,利用它可以驱动浏览器执行特定的动作,如点击、下拉等操作,同时还可以获取浏览器当前呈现的页面的源代码,做到可见即可爬。对于一些JavaScript动态渲染的页面来说,此种抓取方式非常有效。
- PhanttomJS是无界面浏览器,因为老是开着浏览器,不方便.
- Chrome是个浏览器
2.目标站点分析
本节中,我们要利用Selenium抓取淘宝商品并用pyquery解析得到商品的图片、名称、价格、购买人数、店铺名称和店铺所在地信息,并将其保存到MongoDB。
- 打开淘宝网:https://www.taobao.com/
- 搜索一个关键词:美食,看到非常好多吃的,我们现在就是要爬取这些内容.
-
打开淘宝页面,搜索商品,比如美食,此时打开开发者工具,截获Ajax请求,我们可以发现获取商品列表的接口,如图7-19所示。但是这个Ajax接口包含几个参数,参数不能直接发现其规律,如果要去探寻它的生成规律,也不是做不到,但这样相对会比较烦琐,所以如果直接用Selenium来模拟浏览器的话,就不需要再关注这些接口参数了,只要在浏览器里面可以看到的,都可以爬取。这也是我们选用Selenium爬取淘宝的原因
- 爬取的顺序是:需要模拟在输入框输入关键词,点击搜索按钮,获取首页的内容,模拟点击翻页,获得每页源代码,并分析商品信息,存储到MongoDB数据库.
4.1声明浏览器对象
4.1.1本次案例声明浏览器方法
from selenium import webdriver
browser = webdriver.Chrome()
Chrome浏览器会自动打开,如下截图,则说明驱动Chrome浏览器成功:
4.1.2声明浏览器拓展学习内容
Selenium支持非常多的浏览器,如Chrome、Firefox、Edge等,还有Android、BlackBerry等手机端的浏览器。另外,也支持无界面浏览器PhantomJS。
此外,我们可以用如下方式初始化:
browser = webdriver.Chrome()
browser = webdriver.Firefox()
browser = webdriver.Edge()
browser = webdriver.PhantomJS()
browser = webdriver.Safari()
这样就完成了浏览器对象的初始化并将其赋值为browser对象。接下来,我们要做的就是调用browser对象,让其执行各个动作以模拟浏览器操作。
4.2定义搜索的方法
4.2.1定义搜索的方法-search()
- 这里首先构造了一个WebDriver对象,使用的浏览器是Chrome,然后指定一个关键词,如美食.
- 等待加载时,我们使用了WebDriverWait对象,它可以指定等待条件,同时指定一个最长等待时间,这里指定为最长10秒。如果在这个时间内成功匹配了等待条件,也就是说页面元素成功加载出来了,就立即返回相应结果并继续向下执行,否则到了最大等待时间还没有加载出来时,就直接抛出超时异常。
- 比如,我们最终要等待商品信息加载出来,就指定了presence_of_element_located这个条件,然后传入了#q这个选择器,而这个选择器对应的内容就是搜索商品的输入框.
- 关于搜索美食操作,这里首先获取商品输入框,赋值为input,然后获取“确定”按钮,赋值为submit.
- 调用send_keys()方法将美食填充到输入框中,然后点击“确定”按钮(submit.click())即可。
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
browser = webdriver.Chrome()
wait = WebDriverWait(browser, 10)
def search():
browser.get("https://www.taobao.com/")
#等待浏览器的加载,需要一点时间,判断浏览器是否加载成功的方法,才进行下面的操作
input = wait.until(
EC.presence_of_element_located((By.CSS_SELECTOR, "#q"))#目标是输入框
)
submit = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR,"#J_TSearchForm > div.search-button > button")))
#按钮是可以点击的
input.send_keys("美食") #操作动作
submit.click()
def main():
search()
if __name__=='__main__':
main()
4.2.2selenium的拓展学习内容
- 官方学习资料:Selenium with Python
- Python3网络爬虫开发实战7.1-Selenium的使用
- 显式等待
这里还有一种更合适的显式等待方法,它指定要查找的节点,然后指定一个最长等待时间。如果在规定时间内加载出来了这个节点,就返回查找的节点;如果到了规定时间依然没有加载出该节点,则抛出超时异常。示例如下:
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
browser = webdriver.Chrome()
browser.get('https://www.taobao.com/')
wait = WebDriverWait(browser, 10)
input = wait.until(EC.presence_of_element_located((By.ID, 'q')))
button = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, '.btn-search')))
print(input, button)
这里首先引入WebDriverWait这个对象,指定最长等待时间,然后调用它的until()方法,传入要等待条件expected_conditions。比如,这里传入了presence_of_element_located这个条件,代表节点出现的意思,其参数是节点的定位元组,也就是ID为q的节点搜索框。
这样可以做到的效果就是,在10秒内如果ID为q的节点(即搜索框)成功加载出来,就返回该节点;如果超过10秒还没有加载出来,就抛出异常。
对于按钮,可以更改一下等待条件,比如改为element_to_be_clickable,也就是可点击,所以查找按钮时查找CSS选择器为.btn-search的按钮,如果10秒内它是可点击的,也就是成功加载出来了,就返回这个按钮节点;如果超过10秒还不可点击,也就是没有加载出来,就抛出异常。
运行代码,在网速较佳的情况下是可以成功加载出来的。
4.3获取总页面的页数内容-research()
4.3.1本次案例模拟翻页的方法
- 首先获取页数有多少页,点击美食搜索之后,下一步肯定是要等待页数加载出来,需要再加一个等待的判断,即
-
那么,怎样知道有没有跳转到对应的页码呢?我们可以注意到,成功跳转某一页后,页码都会高亮显示,如图7-25所示。total=wait.until(EC.presence_of_element_located((By.CSS_SELECTOR,"#mainsrp-pager > div > div > div > div.total")))
这里商品的搜索结果一般最大都为100页,要获取每一页的内容,只需要将页码从1到100顺序遍历即可,页码数是确定的。所以,直接在页面跳转文本框中输入要跳转的页码,然后点击“确定”按钮即可跳转到页码对应的页面。
- 使用wait时间过久会出现异常.因此增加try and except:
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
browser = webdriver.Chrome()
wait = WebDriverWait(browser, 10)
def search():
try:
browser.get("https://www.taobao.com/")
#等待浏览器的加载,需要一点时间,判断浏览器是否加载成功的方法,才进行下面的操作
input = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, "#q")))#目标是输入框
submit = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR,"#J_TSearchForm > div.search-button > button")))
#按钮是可以点击的
input.send_keys("美食") #操作动作
submit.click()
total = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR,"#mainsrp-pager > div > div > div > div.total")))
return total.text#返回内容
except TimeoutException:
return search()
def main():
total=search()
print(total)
if __name__=='__main__':
main()
最终显示的结果:
- 需要用正则表达式提取100
total=search()
total=int(re.compile('(\d+)').search(total).group(1))#打印出来可能是个字符串,所以要转义为整数
import re
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
browser = webdriver.Chrome()
wait = WebDriverWait(browser, 10)
def search():
try:
browser.get("https://www.taobao.com/")
#等待浏览器的加载,需要一点时间,判断浏览器是否加载成功的方法,才进行下面的操作
input = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, "#q")))#目标是输入框
submit = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR,"#J_TSearchForm > div.search-button > button")))
#按钮是可以点击的
input.send_keys("美食") #操作动作
submit.click()
total = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR,"#mainsrp-pager > div > div > div > div.total")))
return total.text#返回内容
except TimeoutException:
return search()
def main():
total=search()
total=int(re.compile('(\d+)').search(total).group(1))
print(total)
if __name__=='__main__':
main()
最终显示的结果:
4.3.2获得总页面页数的学习内容
-
获取SELECTOR的目标值方法
4.4循环-遍历每页-next_page(page_number)
-刚才我们所定义的next_page(page_number)方法需要接收参数page,page代表页码。这里我们实现页码遍历即可.
- 其实现非常简单,只需要调用一个for循环即可。这里定义最大的页码数为100,range()方法的返回结果就是1到100的列表,顺序遍历,调用next_page(page_number)方法即可。
- 这样我们的淘宝商品爬虫就完成了,最后调用main()方法即可运行。
4.4.1本次循环的方法
1)首先查看淘宝页面,使用页面跳转的方法有两种,一是使用高亮的第1页,第2页,....下一页,而是直接输入第几页
-方法一 ,不推荐使用,具体原因如下:
这里不直接点击“下一页”的原因是:一旦爬取过程中出现异常退出,比如到50页退出了,此时点击“下一页”时,就无法快速切换到对应的后续页面了。此外,在爬取过程中,也需要记录当前的页码数,而且一旦点击“下一页”之后页面加载失败,还需要做异常检测,检测当前页面是加载到了第几页。整个流程相对比较复杂,所以这里我们直接用跳转的方式来爬取页面。
-
方法二,使用的方式是,直接在页面上写第几页.
2)页面输入框
input = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, "#mainsrp-pager > div > div > div > div.form > input")))
3)页面确定按钮
submit = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR,"#mainsrp-pager > div > div > div > div.form > span.btn.J_Submit")))
4)首先清除页面输入框的内容:
input.clear()#清除页码输入框的内容
完整的代码:
def next_page(page_number):
print("正在翻页",page_number)
try:
input = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, "#mainsrp-pager > div > div > div > div.form > input")))#目标是输入框
submit = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR,"#mainsrp-pager > div > div > div > div.form > span.btn.J_Submit")))
input.clear()#清除页码输入框的内容
input.send_keys(page_number)#输入页码
submit.click()
wait.until(EC.text_to_be_present_in_element((By.CSS_SELECTOR,"#mainsrp-pager > div > div > div > ul > li.item.active > span"),str(page_number)))#判断是否翻页成功,通过判断当前的页数是否正确
get_product()
except TimeoutException:
next_page(page_number)
4.5解析网页-商品列表-get_products()
- 接下来,我们就可以实现get_products()方法来解析商品列表了。这里我们直接获取页面源代码,然后用pyquery进行解析
- 首先,调用page_source属性获取页码的源代码,然后构造了PyQuery解析对象,接着提取了商品列表,此时使用的CSS选择器是#mainsrp-itemlist .items .item,它会匹配整个页面的每个商品。它的匹配结果是多个,所以这里我们又对它进行了一次遍历,用for循环将每个结果分别进行解析,每次循环把它赋值为item变量,每个item变量都是一个PyQuery对象,然后再调用它的find()方法,传入CSS选择器,就可以获取单个商品的特定内容了。
- 可以发现,它是一个img节点,包含id、class、data-src、alt和src等属性。这里之所以可以看到这张图片,是因为它的src属性被赋值为图片的URL。把它的src属性提取出来,就可以获取商品的图片了。不过我们还注意data-src属性,它的内容也是图片的URL,观察后发现此URL是图片的完整大图,而src是压缩后的小图,所以这里抓取data-src属性来作为商品的图片。
- 因此,我们需要先利用find()方法找到图片的这个节点,然后再调用attr()方法获取商品的data-src属性,这样就成功提取了商品图片链接。然后用同样的方法提取商品的价格、成交量、名称、店铺和店铺所在地等信息,接着将所有提取结果赋值为一个字典product,随后调用save_to_mongo()将其保存到MongoDB即可。
- 保存到MongoDB
4.5.1本次解析的方法
-
商品的代码结构,如下:
-
判断页面商品信息是否加载成功
wait.until(EC.presence_of_element_located((By.CSS_SELECTOR,"#mainsrp-itemlist .items .item "))) -
获得页面所有选择的内容:
items=doc('#mainsrp-itemlist .items .item').items() -
分析商品图片:
"image" :item.find(".pic .img").attr("src") -
解析商品价格
"price" :item.find(".price").text() -
解析商品成交量
"deal" :item.find(".deal-cnt").text()[:-3]切片到倒数第三个 -
解析商品标题
"title" :item.find(".title").text() -
解析商品店铺名称
"shop" :item.find(".shop").text() -
解析商品店铺地址
"location" :item.find(".location").text()-完整的代码:
# -*- coding: utf-8 -*-
"""
Created on Sat Feb 10 18:33:26 2018
@author: Administrator
"""
import re
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from pyquery import PyQuery as pq
browser = webdriver.Chrome()
wait = WebDriverWait(browser, 10)
def search():
try:
browser.get("https://www.taobao.com/")
#等待浏览器的加载,需要一点时间,判断浏览器是否加载成功的方法,才进行下面的操作
input = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, "#q")))#目标是输入框
submit = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR,"#J_TSearchForm > div.search-button > button")))
#确定按钮
input.send_keys("美食") #操作动作
submit.click()
total = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR,"#mainsrp-pager > div > div > div > div.total")))
get_product()
return total.text#返回内容
except TimeoutException:
return search()
def next_page(page_number):
try:
input = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, "#mainsrp-pager > div > div > div > div.form > input")))#目标是输入框
submit = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR,"#mainsrp-pager > div > div > div > div.form > span.btn.J_Submit")))
input.clear()#清除页码输入框的内容
input.send_keys(page_number)#输入页码
submit.click()
wait.until(EC.text_to_be_present_in_element((By.CSS_SELECTOR,"#mainsrp-pager > div > div > div > ul > li.item.active > span"),str(page_number)))#判断是否翻页成功,通过判断当前的页数是否正确
get_product()
except TimeoutException:
next_page(page_number)
def get_product():
wait.until(EC.presence_of_element_located((By.CSS_SELECTOR,"#mainsrp-itemlist .items .item")))#判断商品信息是否加载成功
html = browser.page_source#获得网页源代码
doc=pq(html)
items=doc('#mainsrp-itemlist .items .item').items()
for item in items:
product = {
"image" :item.find(".pic .img").attr("src"),
"price" :item.find(".price").text(),
"deal" :item.find(".deal-cnt").text()[:-3],#切片到倒数第三个
"title" :item.find(".title").text(),
"shop" :item.find(".shop").text(),
"location" :item.find(".location").text(),
}
print(product)
def main():
total=search()
total=int(re.compile('(\d+)').search(total).group(1))
for i in range(2,total+1):
next_page(i)
if __name__=='__main__':
main()
-最后显示的结果
4.5.2解析的学习内容
4.6保存在MongoDB
这里首先创建了一个MongoDB的连接对象,然后指定了数据库,随后指定了Collection的名称,接着直接调用insert()方法将数据插入到MongoDB。此处的result变量就是在get_products()方法里传来的product,包含单个商品的信息。
4.6.1本次保存在MongoDB的方法
- 新建一个配置文件叫untitled2.py,并输入以下代码
MONGO_URL ="localhost"#本地数据库
MONGO_DB="taobao"#数据库的名称
MONGO_TABLE="product"#数据库表的名称
- 在之前的文件引入mongodb相关数据
import re
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from pyquery import PyQuery as pq
from untitled2 import *#引入mongodb的配置文件
import pymongo#引入mongodb所有的变量
- 在之前的文件声明mongodb相关信息
client=pymongo.MongoClient(MONGO_URL)
db=client[MONGO_DB]
- 在之前的文件定义mongodb方法:
def save_to_mongo(result):
try:
if db[MONGO_TABLE].insert(result):
print("存储到MONGODB成功",result)
except Exception:
print("存储到MONGODB失败",result)
- 完整的代码为:
import re
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from pyquery import PyQuery as pq
from untitled2 import *#引入mongodb的配置文件
import pymongo
client=pymongo.MongoClient(MONGO_URL)
db=client[MONGO_DB]
browser = webdriver.Chrome()
wait = WebDriverWait(browser, 10)
def search():
try:
browser.get("https://www.taobao.com/")
#等待浏览器的加载,需要一点时间,判断浏览器是否加载成功的方法,才进行下面的操作
input = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, "#q")))#目标是输入框
submit = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR,"#J_TSearchForm > div.search-button > button")))
#确定按钮
input.send_keys("美食") #操作动作
submit.click()
total = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR,"#mainsrp-pager > div > div > div > div.total")))
get_product()
return total.text#返回内容
except TimeoutException:
return search()
def next_page(page_number):
try:
input = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, "#mainsrp-pager > div > div > div > div.form > input")))#目标是输入框
submit = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR,"#mainsrp-pager > div > div > div > div.form > span.btn.J_Submit")))
input.clear()#清除页码输入框的内容
input.send_keys(page_number)#输入页码
submit.click()
wait.until(EC.text_to_be_present_in_element((By.CSS_SELECTOR,"#mainsrp-pager > div > div > div > ul > li.item.active > span"),str(page_number)))#判断是否翻页成功,通过判断当前的页数是否正确
get_product()
except TimeoutException:
next_page(page_number)
def get_product():
wait.until(EC.presence_of_element_located((By.CSS_SELECTOR,"#mainsrp-itemlist .items .item")))#判断商品信息是否加载成功
html = browser.page_source#获得网页源代码
doc=pq(html)
items=doc('#mainsrp-itemlist .items .item').items()
for item in items:
product = {
"image" :item.find(".pic .img").attr("src"),
"price" :item.find(".price").text(),
"deal" :item.find(".deal-cnt").text()[:-3],#切片到倒数第三个
"title" :item.find(".title").text(),
"shop" :item.find(".shop").text(),
"location" :item.find(".location").text(),
}
print(product)
save_to_mongo(product)
def save_to_mongo(result):
try:
if db[MONGO_TABLE].insert(result):
print("存储到MONGODB成功",result)
except Exception:
print("存储到MONGODB失败",result)
def main():
total=search()
total=int(re.compile('(\d+)').search(total).group(1))
for i in range(2,total+1):
next_page(i)
browser.close()#把浏览器关掉
if __name__=='__main__':
main()
配置文件
MONGO_URL ="localhost"
MONGO_DB="taobao"
MONGO_TABLE="product"
运行的时候,同时要将配置文件打开,才可以运行.运行的结果如下:
4.6.2MongoDB的学习内容
4.7使用PhantomJS
4.7.1本次使用PhantomJS的方法
- 因用模拟浏览器打开,有点麻烦,所以改为用PhanttomJS,,是无界面浏览器.
将browser = webdriver.Chrome()改为browser = webdriver.PhantomJS() - 在untitled2.py配置文件增加:
SERVICE_ARGS=["--load-images=false","--disk-cache=true"]#是数组形式,不加载浏览器图片,开启缓存 - 在之前文件,增加配置文的变量引入:
browser = webdriver.PhantomJS(service_args=SERVICE_ARGS) - 分别在search()/next_page(page_number)分别增加print("正在搜索")/print("正在翻页",page_number)
- 完整的代码为:
# -*- coding: utf-8 -*-
"""
Created on Sat Feb 10 18:33:26 2018
@author: Administrator
"""
import re
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from pyquery import PyQuery as pq
from untitled2 import *#引入mongodb的配置文件
import pymongo
client=pymongo.MongoClient(MONGO_URL)
db=client[MONGO_DB]
browser = webdriver.PhantomJS(service_args=SERVICE_ARGS)
wait = WebDriverWait(browser, 10)
browser.set_window_size(1400,900)#设置窗口大小
def search():
print("正在搜索")
try:
browser.get("https://www.taobao.com/")
#等待浏览器的加载,需要一点时间,判断浏览器是否加载成功的方法,才进行下面的操作
input = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, "#q")))#目标是输入框
submit = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR,"#J_TSearchForm > div.search-button > button")))
#确定按钮
input.send_keys("美食") #操作动作
submit.click()
total = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR,"#mainsrp-pager > div > div > div > div.total")))
get_product()
return total.text#返回内容
except TimeoutException:
return search()
def next_page(page_number):
print("正在翻页",page_number)
try:
input = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, "#mainsrp-pager > div > div > div > div.form > input")))#目标是输入框
submit = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR,"#mainsrp-pager > div > div > div > div.form > span.btn.J_Submit")))
input.clear()#清除页码输入框的内容
input.send_keys(page_number)#输入页码
submit.click()
wait.until(EC.text_to_be_present_in_element((By.CSS_SELECTOR,"#mainsrp-pager > div > div > div > ul > li.item.active > span"),str(page_number)))#判断是否翻页成功,通过判断当前的页数是否正确
get_product()
except TimeoutException:
next_page(page_number)
def get_product():
wait.until(EC.presence_of_element_located((By.CSS_SELECTOR,"#mainsrp-itemlist .items .item")))#判断商品信息是否加载成功
html = browser.page_source#获得网页源代码
doc=pq(html)
items=doc('#mainsrp-itemlist .items .item').items()
for item in items:
product = {
"image" :item.find(".pic .img").attr("src"),
"price" :item.find(".price").text(),
"deal" :item.find(".deal-cnt").text()[:-3],#切片到倒数第三个
"title" :item.find(".title").text(),
"shop" :item.find(".shop").text(),
"location" :item.find(".location").text(),
}
print(product)
save_to_mongo(product)
def save_to_mongo(result):
try:
if db[MONGO_TABLE].insert(result):
print("存储到MONGODB成功",result)
except Exception:
print("存储到MONGODB失败",result)
def main():
total=search()
total=int(re.compile('(\d+)').search(total).group(1))
for i in range(2,total+1):
next_page(i)
browser.close()#把浏览器关掉
if __name__=='__main__':
main()
配置文件代码为:
MONGO_URL ="localhost"
MONGO_DB="taobao"
MONGO_TABLE="product"
SERVICE_ARGS=["--load-images=false","--disk-cache=true"]#是数组形式,不加载浏览器图片,开启缓存
运行的时候,同时要将配置文件打开,才可以运行.运行的结果如下:
4.7.2使用PhantomJS的方法
官方网站:PhantomJS
4.8整体代码的完善
4.7.1本次完善代码的方法
- 在untitled2.py配置文件增加:KEYWORD = "美食"
然后将search()中的input.send_keys("美食")改为input.send_keys("KEYWORD") - 将main(),增加try 和findally
def main():
try:
total=search()
total=int(re.compile('(\d+)').search(total).group(1))
for i in range(2,total+1):
next_page(i)
finally:
browser.close()#把浏览器关掉
完整的代码
@author: Administrator
"""
import re
from selenium import webdriver
from selenium.common.exceptions import TimeoutException
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from pyquery import PyQuery as pq
from untitled2 import *#引入mongodb的配置文件
import pymongo
client=pymongo.MongoClient(MONGO_URL)
db=client[MONGO_DB]
browser = webdriver.PhantomJS(service_args=SERVICE_ARGS)
wait = WebDriverWait(browser, 10)
browser.set_window_size(1400,900)#设置窗口大小
def search():
print("正在搜索")
try:
browser.get("https://www.taobao.com/")
#等待浏览器的加载,需要一点时间,判断浏览器是否加载成功的方法,才进行下面的操作
input = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, "#q")))#目标是输入框
submit = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR,"#J_TSearchForm > div.search-button > button")))
#确定按钮
input.send_keys("KEYWORD") #操作动作
submit.click()
total = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR,"#mainsrp-pager > div > div > div > div.total")))
get_product()
return total.text#返回内容
except TimeoutException:
return search()
def next_page(page_number):
print("正在翻页",page_number)
try:
input = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, "#mainsrp-pager > div > div > div > div.form > input")))#目标是输入框
submit = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR,"#mainsrp-pager > div > div > div > div.form > span.btn.J_Submit")))
input.clear()#清除页码输入框的内容
input.send_keys(page_number)#输入页码
submit.click()
wait.until(EC.text_to_be_present_in_element((By.CSS_SELECTOR,"#mainsrp-pager > div > div > div > ul > li.item.active > span"),str(page_number)))#判断是否翻页成功,通过判断当前的页数是否正确
get_product()
except TimeoutException:
next_page(page_number)
def get_product():
wait.until(EC.presence_of_element_located((By.CSS_SELECTOR,"#mainsrp-itemlist .items .item")))#判断商品信息是否加载成功
html = browser.page_source#获得网页源代码
doc=pq(html)
items=doc('#mainsrp-itemlist .items .item').items()
for item in items:
product = {
"image" :item.find(".pic .img").attr("src"),
"price" :item.find(".price").text(),
"deal" :item.find(".deal-cnt").text()[:-3],#切片到倒数第三个
"title" :item.find(".title").text(),
"shop" :item.find(".shop").text(),
"location" :item.find(".location").text(),
}
print(product)
save_to_mongo(product)
def save_to_mongo(result):
try:
if db[MONGO_TABLE].insert(result):
print("存储到MONGODB成功",result)
except Exception:
print("存储到MONGODB失败",result)
def main():
try:
total=search()
total=int(re.compile('(\d+)').search(total).group(1))
for i in range(2,total+1):
next_page(i)
finally:
browser.close()#把浏览器关掉
if __name__=='__main__':
main()
配置文件:
MONGO_URL ="localhost"
MONGO_DB="taobao"
MONGO_TABLE="product"
SERVICE_ARGS=["--load-images=false","--disk-cache=true"]#是数组形式,不加载浏览器图片,开启缓存
KEYWORD = "美食"
链接:https://www.jianshu.com/p/cdf916f30d1f
五.使用Redis+Flask维护动态Cookies池以及动态代理池.
在做爬虫的时候,可能会遇到IP被封的问题,利用代理就可以伪装自己的IP进行爬虫请求。在做爬虫请求的时候需要很多代理IP,所以我们可以建立一个代理池,对代理池中的IP进行定期的检查和更新,保证里面所有的代理都是可用的。这里我们使用Redis和Flask维护一个代理池,Redis主要用来提供代理池的队列存储,Flask是用来实现代理池的一个接口,用它可以从代理池中拿出一个代理,即通过web形式把代理返回过来,就可以拿到可用的代理了。
(1)为什么要用代理池
许多网站有专门的反爬虫措施,可能遇到封IP等问题。
互联网上公开了大量免费代理,要利用好资源。
通过定时的检测维护同样可以得到多个可用代理。
(2)代理池的要求
多站抓取,异步检测
定时筛选,持续更新
提供接口,易于提取
(3)代理池架构
架构最核心的部分是“代理队列”,我们要维护的就是这个队列,里面存了很多代理,队列可以用python的数据结构来存,也可以用数据库来存。维护好队列我们需要做两件事情:第一,就是向队列里添加一些可用的代理,获取器从各大网站平台上把代理抓取下来,临时存到一个数据结构里面,然后用过滤器对这些代理进行筛选。筛选的方法也很简单,拿到代理之后,用它请求百度之类的网站,如果可以正常地请求网站,就说明代理可用,否则就将它剔除。过滤完之后将剩余可用的代理放入代理队列。第二,就是对代理队列进行定时检测,因为经过一段时间之后,代理队列里的部分代理可能已经失效,这就需要定时地从里面拿出一些代理,重新进行检测,保留可用的代理,剔除已经失效的代理。最后我们还需要做一个API,通过接口的形式拿到代理队列里面的一些代理。
(4)代理池实现
项目参考来源:https://github.com/germey/proxypool
修改后的程序可以直接下载
各模块功能
getter.py
爬虫模块
class proxypool.getter.FreeProxyGetter
爬虫类,用于抓取代理源网站的代理,用户可复写和补充抓取规则。
schedule.py
调度器模块
class proxypool.schedule.ValidityTester
异步检测类,可以对给定的代理的可用性进行异步检测。
class proxypool.schedule.PoolAdder
代理添加器,用来触发爬虫模块,对代理池内的代理进行补充,代理池代理数达到阈值时停止工作。
class proxypool.schedule.Schedule
代理池启动类,运行RUN函数时,会创建两个进程,负责对代理池内容的增加和更新。
db.py
Redis数据库连接模块
class proxypool.db.RedisClient
数据库操作类,维持与Redis的连接和对数据库的增删查该,
error.py
异常模块
class proxypool.error.ResourceDepletionError
资源枯竭异常,如果从所有抓取网站都抓不到可用的代理资源,则抛出此异常。
class proxypool.error.PoolEmptyError
代理池空异常,如果代理池长时间为空,则抛出此异常。
api.py
API模块,启动一个Web服务器,使用Flask实现,对外提供代理的获取功能。
utils.py
工具箱
setting.py
设置
使用示例
import os
import sys
import requests
from bs4 import BeautifulSoup
dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, dir)
def get_proxy():
r = requests.get('http://127.0.0.1:5000/get')
proxy = BeautifulSoup(r.text, "lxml").get_text()
return proxy
def crawl(url, proxy):
proxies = {'http': proxy}
r = requests.get(url, proxies=proxies)
return r.text
def main():
proxy = get_proxy()
html = crawl('http://docs.jinkan.org/docs/flask/', proxy)
print(html)
if __name__ == '__main__':
main()
Last but not least:
如果程序运行过程中出现错误,很有可能是部分代理网站发生了变化,但“getter.py”文件里请求代理网站程序没有更新导致的。比如有的代理网站不再能够访问,或网站不能正常请求,返回503之类的错误,就需要对程序作出更改,或者直接去掉不能正常访问的网站,重新找一些新的可用的代理网站加进去。
另外,这套程序还有一个不足之处,就是代理池中的代理IP很有可能是重复的,而且重复率会随着运行时间的增加而提高。要解决这个问题,一个是可以增加代理网站的数量,使代理池中的代理有更丰富的来源,此外,还可以在向代理池中增加新的代理时进行重复性检查,如果代理池中已经有该代理IP,则放弃存入代理池。
PS:针对代理池中的代理IP可能会重复的问题,提出了一种解决方法,实测可行。
代理IP之所以会重复,和Redis数据库使用的数据结构有很大关系,原程序使用的是列表(list)结构,数据以列表形式存入数据库后是有序但允许重复的,当有新的数据存入时,并不会对数据的重复性进行检查和处理。但Redis不仅有列表结构,常见的Redis数据结构有String、Hash、List、Set(集合)和Sorted Set(有序集合),使用Set和Sorted Set结构就不会出现重复元素。
Set是无序集合,元素无序排列,当有重复元素存入时,数据库是不会发生变化的;Sorted Set是有序集合,有序集合是可排序的,但是它和列表使用索引下标进行排序依据不同的是,它给每个元素设置一个分数(score)作为排序的依据,当存入一个元素时,同时需要存入该元素的分数。
Sorted Set使用起来较复杂,主要是分数分配问题比较难搞,所以这里使用Set代替原程序中的List作为数据库的数据结构,将以下内容代替原来的“db.py”文件中的内容即可:
# db.py
import redis
from proxypool.error import PoolEmptyError
from proxypool.setting import HOST, PORT, PASSWORD
class RedisClient(object):
def __init__(self, host=HOST, port=PORT):
if PASSWORD:
self._db = redis.Redis(host=host, port=port, password=PASSWORD)
else:
self._db = redis.Redis(host=host, port=port)
def get(self, count=1):
"""
get proxies from redis
"""
proxies = []
for i in range(count):
proxies.append(self._db.spop("proxies"))
return proxies
def put(self, proxy):
"""
add proxy to right top
"""
self._db.sadd("proxies", proxy)
def pop(self):
"""
get proxy from right.
"""
try:
return self._db.spop("proxies").decode('utf-8')
except:
raise PoolEmptyError
@property
def queue_len(self):
"""
get length from queue.
"""
return self._db.scard("proxies")
def flush(self):
"""
flush db
"""
self._db.flushall()
if __name__ == '__main__':
conn = RedisClient()
print(conn.pop())
将数据结构改为Set以后,便不会出现代理池中代理IP重复的问题,但这样做也是有弊端的,因为Set是无序的,所以更新代理池的过程中每次弹出的代理IP也是随机的,这样代理池中的某些代理可能永远也不会被更新,而我们获取代理时采用pop方法得到的也是代理池中随机弹出的代理,该代理有可能是很久没有被更新的已经失效的代理。
总结一下就是使用Set结构可以保证代理池中的代理不会重复,但不能保证调用代理池获取代理时得到的代理是最新的和可用的,而List结构可以保证当前获取的代理是最新的,但代理池中的代理可能会有很大的重复。总之,两种方法都是有利有弊的,当然也可以尝试用有序集合(Sorted Set)构建一种完美的方法了。
参考内容:
python redis-string、list、set操作
Python操作redis学习系列之(集合)set,redis set详解 (六)
Redis 有序集合
redis学习笔记(三):列表、集合、有序集合
---------------------
原文:https://blog.csdn.net/polyhedronx/article/details/81485458?utm_source=copy
(1)为什么要用Cookies池?
有些网站需要登录才能爬取,例如新浪微博
爬取过程中如果频率过高会导致封号
需要维护多个账号的Cookies池实现大规模爬取
(2)Cookies池的要求
自动登录更新
定时验证筛选
提供外部接口
(3)Cookies池架构
Cookies池的架构采用下图所示的形式:
首先,需要有一个账号队列,把一些账号密码存到数据库里,生成器即程序从队列里面拿出账号密码,自动地进行登录,并获取登录的Cookies,然后放到Cookies队列里。定时检测器从Cookies队列里定期地随机选出一些Cookies,并用这些Cookies请求网页,如果请求成功就放回队列,否则从队列里剔除,这样就能做到实时更新,保证Cookies队列里的Cookies都是可用的。此外,还需要提供一个API接口,使外部程序能够从队列里获取到Cookies。
(4)Cookies池的实现
项目参考来源:
https://github.com/Germey/CookiesPool
https://github.com/Python3WebSpider/CookiesPool
可扩展的Cookies池,目前对接了新浪微博,m.weibo.cn,可自行扩展其他站点。
安装
pip3 install -r requirements.txt
基础配置
接口基本配置
# Redis数据库地址
REDIS_HOST = 'localhost'
# Redis端口
REDIS_PORT = 6379
# Redis密码,如无填None
REDIS_PASSWORD = 'foobared'
# 产生器使用的浏览器
BROWSER_TYPE = 'Chrome'
# 产生器类,如扩展其他站点,请在此配置
GENERATOR_MAP = {
'weibo': 'WeiboCookiesGenerator'
}
# 测试类,如扩展其他站点,请在此配置
TESTER_MAP = {
'weibo': 'WeiboValidTester'
}
# 检测器检测接口
TEST_URL_MAP = {
'weibo': 'https://m.weibo.cn/api/container/getIndex?uid=1804544030&type=uid&page=1&containerid=1076031804544030'
}
# 产生器和验证器循环周期
CYCLE = 120
# API地址和端口
API_HOST = '0.0.0.0'
API_PORT = 5000
进程开关
在config.py修改
# 产生器开关,模拟登录添加Cookies
GENERATOR_PROCESS = True
# 验证器开关,循环检测数据库中Cookies是否可用,不可用删除
VALID_PROCESS = True
# API接口服务
API_PROCESS = True
账号购买
账号可在淘宝购买。
导入账号
python3 importer.py
请输入账号密码组, 输入exit退出读入
18459748505----astvar3647
14760253606----gmidy8470
14760253607----uoyuic8427
18459749258----rktfye8937
账号 18459748505 密码 astvar3647
录入成功
账号 14760253606 密码 gmidy8470
录入成功
账号 14760253607 密码 uoyuic8427
录入成功
账号 18459749258 密码 rktfye8937
录入成功
exit
运行
请先导入一部分账号之后再运行,运行命令:
python3 run.py
运行效果
三个进程全部开启:
API接口开始运行
* Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
Cookies生成进程开始运行
Cookies检测进程开始运行
正在生成Cookies 账号 14747223314 密码 asdf1129
正在测试Cookies 用户名 14747219309
Cookies有效 14747219309
正在测试Cookies 用户名 14740626332
Cookies有效 14740626332
正在测试Cookies 用户名 14740691419
Cookies有效 14740691419
正在测试Cookies 用户名 14740618009
Cookies有效 14740618009
正在测试Cookies 用户名 14747222472
Cookies有效 14747222472
Cookies检测完成
验证码位置 420 580 384 544
成功匹配
拖动顺序 [1, 4, 2, 3]
成功获取到Cookies {'SUHB': '08J77UIj4w5n_T', 'SCF': 'AimcUCUVvHjswSBmTswKh0g4kNj4K7_U9k57YzxbqFt4SFBhXq3Lx4YSNO9VuBV841BMHFIaH4ipnfqZnK7W6Qs.', 'SSOLoginState': '1501439488', '_T_WM': '99b7d656220aeb9207b5db97743adc02', 'M_WEIBOCN_PARAMS': 'uicode%3D20000174', 'SUB': '_2A250elZQDeRhGeBM6VAR8ifEzTuIHXVXhXoYrDV6PUJbkdBeLXTxkW17ZoYhhJ92N_RGCjmHpfv9TB8OJQ..'}
成功保存Cookies
--------------------
原文:https://blog.csdn.net/polyhedronx/article/details/81584618?utm_source=copy
六.使用Scrapy+Cookies池抓取新浪微博
一、什么是Scrapy
Scrapy是Python开发的一个快速、高层次的屏幕抓取和web抓取框架,用于抓取web站点并从页面中提取结构化的数据。Scrapy用途广泛,可以用于数据挖掘、监测和自动化测试。
Scrapy吸引人的地方在于它是一个框架,任何人都可以根据需求方便的修改。它也提供了多种类型爬虫的基类,如BaseSpider、sitemap爬虫等,最新版本又提供了web2.0爬虫的支持。
二、直接安装
下面介绍在Windows下安装Scrapy的过程。
在Windows下安装Scrapy框架是非常麻烦的,需要安装许多依赖库,万一有一个依赖库没有安装好就会导致安装失败。安装过程可以分为以下几步:
1.安装wheel
pip install wheel
第一步需要安装wheel,安装好wheel库之后,你就可以安装一些wheel文件,通过这些wheel文件完成其他库的安装。因为我已经安装过了,所以会显示以下内容。
2. 安装lxml
第二步需要安装lxml,通过这个链接http://www.lfd.uci.edu/~gohlke/pythonlibs/#lxml,可以找到很多不同版本的wheel文件,由于我使用的是python3.6,所以就下载了下面这个文件。
直接点击就可以下载,下载完成后找到该文件,右键-->属性-->安全,把对象名称,也就是文件路径copy下来,然后在CMD窗口输入“pip install copy的路径”就可以了。
3.PyOpenssl
第三步需要安装PyOpenssl,通过链接https://pypi.python.org/pypi/pyOpenSSL#downloads,进入pypi网站,点击“Download files”,然后点击下载wheel文件即可。
接着,同样把文件路径(包括文件名称)复制下来,在CMD输入“pip install copy的路径”进行安装。
4.安装Twisted
第四步需要安装Twisted框架,这是一个异步框架,Scrapy的核心就是基于Twisted框架。同样地,访问http://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted,下载对应版本的wheel文件。
下面依然是复制文件路径,并用pip进行安装。
5.安装Pywin32
接下来需要安装Pywin32文件,它是python和win32在运行过程中兼容的一个库。访问https://sourceforge.net/projects/pywin32/files/pywin32/Build%20220/,打开之后会看到一个下载列表,这里点击下载3.6版本。
下载完成会看到一个exe文件,运行之,依次点击下一步,然后点击完
成。
6.安装Scrapy
安装完依赖库之后,最后一步安装Scrapy框架就Ok了。
pip install scrapy
7.测试Scrapy
最后对安装完的Scrapy框架进行测试,比如新建一个“hello”项目:输入“scrapy startproject hello”:
根据提示依次输入:
cd hello
scrapy genspider baidu www.baidu.com
然后它给我们创建了一个spider:
接下来我们输入“scrapy crawl baidu”,运行一下这个spider,如果运行过程没有出错的话,就说明Scrapy框架安装没问题。
三、Anaconda平台下的安装
Anaconda是一个科学计算环境,它里面给我们提供了许多库。如果装有Anaconda,就可以用“conda”命令安装Scrapy。
用conda安装是非常简单的,只需要输入“conda install scrapy”就可以了,出现提示后输入“y”。
---------------------
原文:https://blog.csdn.net/polyhedronx/article/details/82461925?utm_source=copy
使用方法:https://blog.csdn.net/qq_42156420/article/details/80746774
前面讲解了Scrapy中各个模块基本使用方法以及代理池、Cookies池。接下来我们以一个反爬比较强的网站新浪微博为例,来实现一下Scrapy的大规模爬取。
一、本节目标
本次爬取的目标是新浪微博用户的公开基本信息,如用户昵称、头像、用户的关注、粉丝列表以及发布的微博等,这些信息抓取之后保存至MongoDB。
二、准备工作
请确保前文所讲的代理池、Cookies池已经实现并可以正常运行,安装Scrapy、PyMongo库。
三、爬取思路
首先我们要实现用户的大规模爬取。这里采用的爬取方式是,以微博的几个大V为起始点,爬取他们各自的粉丝和关注列表,然后获取粉丝和关注列表的粉丝和关注列表,以此类推,这样下去就可以实现递归爬取。如果一个用户与其他用户有社交网络上的关联,那他们的信息就会被爬虫抓取到,这样我们就可以做到对所有用户的爬取。通过这种方式,我们可以得到用户的唯一ID,再根据ID获取每个用户发布的微博即可。
四、爬取分析
这里我们选取的爬取站点是:https://m.weibo.cn,此站点是微博移动端的站点。打开该站点会跳转到登录页面,这是因为主页做了登录限制。不过我们可以绕过登录限制,直接打开某个用户详情页面,例如打开周冬雨的微博,链接为:https://m.weibo.cn/u/1916655407,即可进入其个人详情页面,如下图所示
我们在页面最上方可以看到周冬雨的关注和粉丝数量。我们点击关注,进入到她的关注列表,如下图所示。
我们打开开发者工具,切换到XHR过滤器,一直下拉关注列表,即可看到下方会出现很多Ajax请求,这些请求就是获取周冬雨的关注列表的Ajax请求,如下图所示。
我们打开第一个Ajax请求,它的链接为:https://m.weibo.cn/api/container/getIndex?containerid=231051-_followers-_1916655407&luicode=10000011&lfid=1005051916655407&featurecode=20000320&type=uid&value=1916655407&page=2,详情如下图所示。
请求类型是GET类型,返回结果是JSON格式,我们将其展开之后即可看到其关注的用户的基本信息。接下来我们只需要构造这个请求的参数。此链接一共有7个参数,如下图所示。
其中最主要的参数就是containerid
和page
。有了这两个参数,我们同样可以获取请求结果。我们可以将接口精简为:https://m.weibo.cn/api/container/getIndex?containerid=231051-_followers-_1916655407&page=2,这里的container_id
的前半部分是固定的,后半部分是用户的id。所以这里参数就可以构造出来了,只需要修改container_id
最后的id
和page
参数即可获取分页形式的关注列表信息。
利用同样的方法,我们也可以分析用户详情的Ajax链接、用户微博列表的Ajax链接,如下所示:
# 用户详情API
user_url = 'https://m.weibo.cn/api/container/getIndex?uid={uid}&type=uid&value={uid}&containerid=100505{uid}'
# 关注列表API
follow_url = 'https://m.weibo.cn/api/container/getIndex?containerid=231051_-_followers_-_{uid}&page={page}'
# 粉丝列表API
fan_url = 'https://m.weibo.cn/api/container/getIndex?containerid=231051_-_fans_-_{uid}&page={page}'
# 微博列表API
weibo_url = 'https://m.weibo.cn/api/container/getIndex?uid={uid}&type=uid&page={page}&containerid=107603{uid}'
详情:https://blog.csdn.net/m0_37438418/article/details/80819847
具体操作:http://www.cnblogs.com/bep-feijin/p/9325184.html
七.使用Scrapy分布式架构抓取知乎文章
https://blog.csdn.net/qq_40717846/article/details/79014132
更多推荐
所有评论(0)