最近需要做一个爬去公司内部网页上新闻的工程,要爬取内部网页就需要连接内网,然而非公司配置的电脑是没法直接通过连接网线访问网络的,因此需要通过代理访问公司内的网站以及外部网站。最开始毫无头绪,直到发现了公司提供的pac文件。PAC文件其实是一个类JavaScript脚本的文件,可以让浏览器使用pac自动配置代理,也可以人工读出pac的内容并找到合适的代理服务器,具体点就是找到return的内容,一般会返回一个ip地址以及端口号,这就是我们接下来做爬虫时需要的代理服务器。
废话不多说直接进入代码部分。
首先是再terminal通过scrapy startproject news建立scrapy项目,这点无需多讲。要使我们的爬虫通过代理,最重要的是配置middleware.py文件,默认情况下middleware下面有两个class,一个是xxxSpiderMiddleWare,这个我们暂且不用管,我们要找的是xxxDownloaderMiddleWare,在这个class之下找到process_request方法,并重写为
def process_request(self, request, spider):
request.meta['proxy']="http://server's ip:port"
proxy_user_pass="username:password"
encoded_proxy=base64.b64encode(proxy_user_pass.encode('utf-8'))
request.headers['Proxy-Authorization']='Basic '+str(encoded_proxy,encoding="utf-8")
在request.meta[‘proxy’]后面填写你获得的ip地址及端口号,如果代理需要认证的话就在proxy_user_pass后面写入你的用户名及密码。后面encode部分其他的文章是使用base64.encodestring 这个方法,然而在python3下是不可行的,会报
TypeError: expected bytes-like object, not str
再有一点要注意的就是request.headers[‘Proxy-Authorization’]后面的‘Basic ’字段里是有空格的,即‘Basic空格’,这个大意不得,一开始没注意导致代理一直认证不成功。
设置完代理就要在SETTING.py文件下启用DownloaderMiddleWare, 就直接把那一行文字前面的注释符去掉就成。然后我们的爬虫就可以通过代理进行爬取工作。
既然这个工程项目是爬去新闻内容,就免不了爬取图片这件事,使用urllib.request下的urlretrieve()方法是不行的,因为这个并不走代理,然而想到我们既然为整个scrapy进行了代理配置,那么用scrapy自己的方法下载图片应该会省事很多,一番搜索终于找到方法。
首先要启用imagepipeline,做法就是打开pipeline.py
from scrapy.pipelines.images import ImagesPipeline
from scrapy.exceptions import DropItem
如果python没有安装pillow的话可能会报错
“No module named PIL”
解决方法就是安装pillow
pip install pillow
然后在pipeline.py下面新建一个imagepipeline的class:
class NewsImagePipeline(ImagesPipeline):
def get_media_requests(self,item,info):
title=item['title']
yield scrapy.Request(url=item['image_urls'],meta={'title':title})
def item_completed(self,results,item,info):
image_path=[x['path'] for ok,x in results if ok]
if not image_path:
raise DropItem("Item contains no images")
return item
def file_path(self,request,response=None,info=None):
title=request.meta['title']
title.replace(' ','_')
imagename=title+'.jpg'
imagename=imagename.replace(' ','_')
filename=u'full/{0}/{1}'.format('newsimg',imagename)
return filename
这里进行几点解释,首先在定义imagepipeline class的时候一定要继承ImagePipeline。然后在get_media_request方法里面我们读取了item里面的title内容,这是为了接下来为图片自主命名,然后将title通过yield scrapy.Request() 里的meta传递给接下里的函数。file_name函数就是返回一个指定文件的名称及路径。这里我们返回的是…/full/newsimg/图片名称。
一开始在item_completed()方法下面我是没有添加return item的,然后在运行的时候会报错 item (nontype not subscriptable)估计是进行imagepipeline处理之后并没有把item给传递下去,所以在其他的pipeline进行处理的时候就无法接收item的内容。所以只要添加一个return item就好了。
然后还要在item.py下面定义item
class NewsItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
#time=scrapy.Field()
title=scrapy.Field()
summary=scrapy.Field()
image_urls=scrapy.Field()
images=scrapy.Field()
image_paths=scrapy.Field()
content=scrapy.Field()
image-urls在这里是必须定义的
然后在SETTING.py里面启用imagepipeline
ITEM_PIPELINES = {
'news.pipelines.NewsPipeline': 300,
'news.pipelines.NewsImagePipeline':1
}
IMAGES_URLS_FIELD="image_urls"
IMAGES_RESULT_FIELD="image_path"
IMAGES_STORE='/home/pi/news/pic/'
在pipeline后面的数字是处理的优先级,这里我把imagepipeline作为首要任务。IMAGES_STORE是设置保存图片的路径,这个是必须的,否则imagepipeline无法启用。
最后也是最重要的一点就是在spider里面进行搜寻图片的url的工作并把它存储到item[‘image_url’]下面,然后我们的爬虫就可以自动爬取下载图片了。这里代码就不上了,毕竟每个网页都有不同。
————————————————————————————-——
后续:
后来老板要求要按照时间顺序循环在一个网页中展示新闻,一开始心想保存下来的网页名和图片名在文件夹中都不是按照时间排序的,这玩意怎么做,后来一拍脑瓜:直接按照顺序编号01234567给网页和图片命名不就行了。具体做法就是在item.py中创建个“sequence”项:
sequence=scrapy.Field()
因为网页的新闻是由新到旧向下排列的,因此spider在解析整个网页内容的时候爬到的每一个标题也是由新到旧存储在一个列表里,然后在spider文件里面按照顺序对每一个item赋予编号,最终在pipeline文件中获取item[‘sequence’]的值对文件进行相应的编号。
在编写总的循环展示网页的时候我们只需要读取文件夹内文件的总个数,然后按照顺序编号挨个读取网页内容进行展示
创建总网页master.html的代码如下
import os
dirpath="../news/webpage"
lsts=os.listdir(dirpath)
file_path=[]
for file in range(len(lsts)):
path="./news/webpage/"+str(file)+'.html'
file_path.append(path)
s=str(file_path)
with open("../master.html","w",encoding="utf-8") as f:
f.write(
'''<html>
<head>
<meta charset:"UTF-8">
<style>
.dis{width:100%%;height:100%%;margin:auto;border:0px;}
.dis::-webkit-scrollbar{display:none;}
body::-webkit-scrollbar{display:none}
body{background-color:white;overflow:hidden;width:100%%;height:100%%;margin:auto;}
</style>
</head>
<body>
<iframe class="dis" id="visita">
</iframe>
<script>
function change(){
document.getElementById("visita").src=pages[i];
i++;
if(i+1==pages.length+1){i=0}
}
function run(){
setInterval(change,5000)}
run()
</script>
</body>
</html>'''%(s)
)
f.close()
print(s)