discourse/app/jobs/regular/crawl_topic_link.rb
2014-04-08 16:39:47 -04:00

114 lines
3.3 KiB
Ruby

require 'open-uri'
require 'nokogiri'
require 'excon'
module Jobs
class CrawlTopicLink < Jobs::Base
class ReadEnough < Exception; end
# Retrieve a header regardless of case sensitivity
def self.header_for(head, name)
header = head.headers.detect do |k, v|
name == k.downcase
end
header[1] if header
end
def self.request_headers(uri)
{ "User-Agent" => "Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1667.0 Safari/537.36",
"Accept" => "text/html",
"Host" => uri.host }
end
# Follow any redirects that might exist
def self.final_uri(url, limit=5)
return if limit < 0
uri = URI(url)
return if uri.blank? || uri.host.blank?
headers = CrawlTopicLink.request_headers(uri)
head = Excon.head(url, read_timeout: 20, headers: headers)
# If the site does not allow HEAD, just try the url
return uri if head.status == 405
if head.status == 200
uri = nil unless header_for(head, 'content-type') =~ /text\/html/
return uri
end
location = header_for(head, 'location')
if location
location = "#{uri.scheme}://#{uri.host}#{location}" if location[0] == "/"
return final_uri(location, limit - 1)
end
nil
end
def self.max_chunk_size(uri)
# Amazon leaves the title until very late. Normally it's a bad idea to make an exception for
# one host but amazon is a big one.
return 80 if uri.host =~ /amazon\.(com|ca|co.uk)$/
# Default is 10k
10
end
# Fetch the beginning of a HTML document at a url
def self.fetch_beginning(url)
# Never crawl in test mode
return if Rails.env.test?
uri = final_uri(url)
return "" unless uri
result = ""
streamer = lambda do |chunk, remaining_bytes, total_bytes|
result << chunk
# Using exceptions for flow control is really bad, but there really seems to
# be no sane way to get a stream to stop reading in Excon (or Net::HTTP for
# that matter!)
raise ReadEnough.new if result.size > (CrawlTopicLink.max_chunk_size(uri) * 1024)
end
Excon.get(uri.to_s, response_block: streamer, read_timeout: 20, headers: CrawlTopicLink.request_headers(uri))
result
rescue ReadEnough
result
end
def execute(args)
raise Discourse::InvalidParameters.new(:topic_link_id) unless args[:topic_link_id].present?
begin
topic_link = TopicLink.where(id: args[:topic_link_id], internal: false, crawled_at: nil).first
return if topic_link.blank?
crawled = false
result = CrawlTopicLink.fetch_beginning(topic_link.url)
doc = Nokogiri::HTML(result)
if doc
title = doc.at('title').try(:inner_text)
if title.present?
title.gsub!(/\n/, ' ')
title.gsub!(/ +/, ' ')
title.strip!
if title.present?
crawled = topic_link.update_attributes(title: title[0..255], crawled_at: Time.now)
end
end
end
rescue Exception
# If there was a connection error, do nothing
ensure
topic_link.update_column(:crawled_at, Time.now) if !crawled && topic_link.present?
end
end
end
end