网页 抓取 innertext 试题(一个demoP站视频标题连接从哪里来?(图))

优采云 发布时间: 2022-01-06 15:06

  网页 抓取 innertext 试题(一个demoP站视频标题连接从哪里来?(图))

  最近在学习Golang有一段时间了,想写点东西练练手。一开始想用golang来写爬虫,但是感觉太简单了。为什么不写一个网站!想了想,我对P站很熟悉了。文章之前写过一段nginx反代P站的视频。结合起来写个静态P站比较好,所以做了个demo。整个Golang代码不多,非常适合新手练习。

  

  首先分析一下整个网站的思路,内容,也就是图片和视频标题链接,从何而来?这当然是直接从 Pornhub 抓取的。其次,由于是静态的网站,除了获取视频页面外,基本没有与后端的交互。页面也由后端直接渲染到前端。视频的播放由nginx直接处理。大致就是这么简单,其他的细节会在后面详述。

  我们的第一步是去P站抓取视频素材,因为是静态的网站我们不用弄的太复杂,一次只能抓取一个页面显示出来。众所周知,P站是一个学习网站,也是一个包容的网站!比如我们在P站搜索美食时,可以看到很多网友上传的美食视频。

  

  假设我们抓取上面的视频内容,打开控制台查看页面元素。

  

  每个视频都放置在一个li标签中,里面收录了视频的一些基本信息。这是我们需要爬行的。原视频的链接只能从视频页面获取,需要再次发起get请求。将部分代码直接粘贴在这里。

  func getRandomPhid() []string {

rand.Seed(time.Now().Unix())

randNum := rand.Intn(1000) //播种生成随机整数

url := "https://cn.pornhub.com/video?page=" + strconv.Itoa(randNum)

fmt.Println(url)

resp, err := http.Get(url)

checkError(err)

htmlText, err := ioutil.ReadAll(resp.Body)

checkError(err)

root, err := htmlquery.Parse(strings.NewReader(string(htmlText)))

checkError(err)

ulTag := htmlquery.FindOne(root, "//*[@id=\"videoCategory\"]")

aHrefList := htmlquery.Find(ulTag, "//a[@href]")

phList := []string{}

tmp := ""

for _, n := range aHrefList {

aHref := htmlquery.SelectAttr(n, "href")

if strings.Contains(aHref, "viewkey") {

if tmp == aHref { //提取视频链接时会有连续重复的情况

continue

}

ph := strings.Split(aHref, "=")[1]

phList = append(phList, ph)

tmp = aHref

}

}

return phList

}

  这里我定义了一个getRandomPhid的函数,就是随机抓取P站上一页的视频内容。播种后,生成 0-1000 之间的任意整数。直接使用 http.Get 方法是因为不需要对请求进行额外的更改。这里使用了第三方库htmlquery来解析html元素,类似于python中的BeautifulSoup库。

  因为所有li标签的父标签都是ul,所以元素通过xpath定位。然后我定义了一个字符串类型的切片 phList 来存储提取的视频 ID。因为会有重复的id,还是需要去重,但是golang没有python方便,可以直接使用set,需要自己实现,但是这里比较特殊,两个重复的id是连续的,所以直接用一个tmp变量来和前面的比较一下,去掉重复。

  func reqPhid(id string) (videoInfo map[string]string, err error) {

defer func() {

if err := recover(); err != nil {

log.Println(err)

}

}()

client := &http.Client{}

url := "https://cn.pornhub.com/view_video.php?viewkey=" + id

req, err := http.NewRequest("GET", url, nil)

checkError(err)

req.Header.Set("user-agent", "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1")

resp, err := client.Do(req)

checkError(err)

htmlText, err := ioutil.ReadAll(resp.Body)

checkError(err)

root, err := htmlquery.Parse(strings.NewReader(string(htmlText)))

checkError(err)

js := htmlquery.FindOne(root, "//*[@id=\"mobileContainer\"]/script[1]/text()")

jsString := htmlquery.InnerText(js)

videoInfo = make(map[string]string)

phQuality := gjson.Get(jsString, "quality_720p").String()

if phQuality == "" {

err = errors.New("获取视频链接失败!")

return

}

re := regexp.MustCompile(`(?m)https:\/\/([a-z0-9]{2,3})\.phncdn\.com`)

subDomain := re.FindStringSubmatch(phQuality)[1]

if subDomain != "dm1" {

time.Sleep(3 * time.Second)

err = errors.New("未匹配dm1接口!")

return

}

phQuality = re.ReplaceAllString(phQuality, domain+subDomain)

image_url := gjson.Get(jsString, "image_url").String()

subDomain = re.FindStringSubmatch(image_url)[1]

image_url = re.ReplaceAllString(image_url, domain+subDomain)

video_title := gjson.Get(jsString, "video_title").String()

videoInfo["videoUrl"] = phQuality

videoInfo["imgUrl"] = image_url

videoInfo["title"] = video_title

fmt.Println(videoInfo)

return

}

  在前面的函数中,我们得到了所有的视频 ID。要获得真正的视频链接,我们需要再次请求视频页面。在这个函数中,我从一开始就推迟了一个匿名函数来捕获异常。接下来我们没有使用http.Get方法,而是使用&http.Client{}添加了一些我们自定义的header,即修改User-Agent伪装成手机浏览器header,而这个server也返回h5页面,原因是这里用了一个小技巧。

  看过我文章的读者都知道,PC端请求P站得到的视频信息是经过加密和混淆处理的,手机端的信息直接是原创视频信息。我偶然发现了这一点,否则就太费力了,吃力不讨好,所以这个想法还是很重要的,有时也能奏效。不知道的朋友可以看看我之前的文章。

  看完Pornhub的视频界面JS混乱,我手写了一个下载插件

  可以试试用Chrome切换浏览器UA,真的很方便!

  

  或者先用xpath定位这个JS的位置。flashvars的变量显然是json格式的数据。U1S1,golang原生处理json的方法真的很麻烦,需要写一个struct来接受数据。小数据的json也可以,一些复杂的json,尤其是不知道数据类型的时候,直接破解。这里推荐一个第三方模块gjson来解析,非常方便,和python一样流畅。

  另外视频的分辨率有240、480、720、1080,为了尽量简化,我只拍了720分辨率的链接。通过定期匹配,将视频链接转换为您自己的域名。最后,它返回一个 map[string]string 类型的数据。

   re := regexp.MustCompile(`(?m)https:\/\/([a-z0-9]{2,3})\.phncdn\.com`)

subDomain := re.FindStringSubmatch(phQuality)[1]

if subDomain != "dm1" {

time.Sleep(3 * time.Second)

err = errors.New("未匹配dm1接口!")

return

}

  至于为什么这里只能使用dm1接口链接,我们后面再讨论。并且为了防止请求频率因为请求频率过快而被禁止,每次爬取后,暂停3s,尽可能减少异常。

  func timeToRefresh() {

count := 0 //记录页面刷新的次数

for true {

allVideos = make([]map[string]string, 0)

phList := getRandomPhid()

for _, id := range phList {

video, err := reqPhid(id)

if err != nil {

log.Println(err)

continue

}

allVideos = append(allVideos, video)

}

count++

log.Printf("第%v次刷新页面", count)

time.Sleep(15 * time.Minute) //每隔多长时间时间刷新一次页面

}

}

  因为我们写的是静态页面,没有前后端交互,而且后端渲染的很好,所以后端要定时抓取来刷新我们网站的内容. 这里allVideos是一个map,用来存放每次捕捉到的视频信息,但是注意这个变量在for循环中每次都用make初始化,相当于每次刷新视频数据。同时 allVideos 仍然是 myHandler 直接使用的全局变量。

  在这里获取所有与视频相关的信息,基本上抓住它就结束了。剩下的就是网络服务器所做的。其实在golang里面很简单。

  func myHandler(w http.ResponseWriter, r *http.Request) {

t, err := template.ParseFiles("index.html")

checkError(err)

err = t.Execute(w, allVideos)

checkError(err)

}

func main() {

go timeToRefresh()

http.HandleFunc("/", myHandler)

err := http.ListenAndServe(":8080", nil)

checkError(err)

}

  这里我把这两个函数放在一起,myHandler相当于对请求的响应。首先是解析我们写好的前端模板,然后渲染生成我们传入的数据并发送给用户。

  前面提到的timeToRefresh函数是一个定时捕获函数,但是我们不能让抓取的页面阻塞我们的主线程,所以直接在它前面加一个go关键字就可以了。是不是很方便?这体现了为了go语言的魅力,我们会另外写一个线程或者其他语言的东西。所以golang写起来真的很流畅。然后是绑定路径和*敏*感*词*端口。这些都非常简单。

  func checkError(err error) {

if err != nil {

log.Println(err)

}

}

  而且大家都知道golang中的错误处理会写一堆if err !=nil{… },所以我再封装一层。在上面的代码中,我看到我使用 checkError 统一处理 err。

  Golang的部分基本完成。再说说Nginx的部分。既然要播放视频,就不能随便抢视频链接,因为P站在国内被屏蔽了,不能直接播放。一切都可以通过使用Nginx反生成视频流打开缓冲区来实现。具体可以参考我之前的文章文章。

  白嫖?神奇使用Nginx proxy_buffer实现Pornhub视频反向生成

  在这个文章中,我使用了多个子域来逆向生成。事实上,读者无需在此之前发表评论。您可以使用以下编写方法。

  

  前面的代码提到了不匹配dm1接口的时候会panic,因为我这里没有研究透彻。我把dm1域名倒过来是可以的,但是其他的会以403拒绝响应。

  

  dm1 是正常的。所以这里我只使用了匹配dm1的视频链接。

  

  我猜应该是做了一些测试,但是我想通了具体的原因,不知道有没有必要带一些具体的header。希望朋友们留言一起讨论。

  最后是前端模板。这是勾刀为我做的。非常感谢勾刀的帮助。其中 let json = {{.}} 是 golang 模板的语法,传入我们的数据。

  

Golang + Nginx 实现静态 Pornhub

//样式的代码过长,就不展示了

每隔15分钟自动刷新视频页面

//后端返回的数据

let json = {{.}}

//处理数据

json.map(v => {

appendCard(newCard(v.videoUrl, v.imgUrl, v.title))

})

//创建card

function newCard(videoUrl, imgUrl, title) {

let card = document.createElement('div')

card.classList.add('card')

let face1 = document.createElement('div')

face1.classList.add('face')

face1.classList.add('face1')

let p = document.createElement('p')

p.innerText = title

p.setAttribute("videoUrl", videoUrl)

face1.appendChild(p)

let face2 = document.createElement('div')

face2.classList.add('face')

face2.classList.add('face2')

let img = document.createElement('img')

img.setAttribute('src', imgUrl)

img.setAttribute('alt', "")

face2.appendChild(img)

card.appendChild(face1)

card.appendChild(face2)

p.onclick = function (e) {

window.open(e.target.getAttribute("videoUrl"))

}

return card

}

//将card添加进页面

function appendCard(card) {

let main = document.getElementsByClassName('main')[0]

main.appendChild(card)

}

  这样我们的静态P站就做好了,最后就是显示我们用Golang+Nginx网站做了。我在某云中使用了一台香港轻量级服务器在这里运行。它的带宽为30m。反代视频基本无压力。

  因为比例太大,这里全部编码完成,网站的背景改为橙色。有没有P站的味道?哈哈哈。同时点击视频直接播放,是不是很香!好像ghs没那么难!当然,这里只是技术分享,哈哈!

  本博客纯粹是我学习Golang一段时间后做的一个小demo。有兴趣的朋友可以自行尝试。毕竟,您可以应用所学!我把代码、前端模板和nginx配置文件放在下面,把golang代码和Nginx配置中的域名换成自己的域名即可!每个人都可以通过简单的改变开始跑步。

  golang+nginx-做一个静态的pornhub网站.zip

  如果有什么问题,小伙伴们可以在下方留言,共同探讨!

0 个评论

要回复文章请先登录注册


官方客服QQ群

微信人工客服

QQ人工客服


线