···11+# Config file for automatic testing at travis-ci.org
22+33+language: python
44+python: 2.7
55+66+# command to install dependencies, e.g. pip install -r requirements.txt --use-mirrors
77+install:
88+ - pip install Scrapy docopt
99+1010+# command to run tests, e.g. python setup.py test
1111+script:
1212+ - nosetests tests
1313+1414+notifications:
1515+ slack: descartes2:6sgCzx3PvrO9IIMwKxj12dDM
+10-8
FourmiCrawler/pipelines.py
···4455from scrapy.exceptions import DropItem
6677-class RemoveNonePipeline(object):
8788+class RemoveNonePipeline(object):
99 def __init__(self):
1010- self.known_values = set()
1010+ pass
11111212- def process_item(self, item, spider):
1212+ @staticmethod
1313+ def process_item(item, spider):
1314 """
1415 Processing the items so None values are replaced by empty strings
1516 :param item: The incoming item
···2122 item[key] = ""
2223 return item
23242424-class DuplicatePipeline(object):
25252626+class DuplicatePipeline(object):
2627 def __init__(self):
2728 self.known_values = set()
2829···3536 """
3637 value = (item['attribute'], item['value'], item['conditions'])
3738 if value in self.known_values:
3838- raise DropItem("Duplicate item found: %s" % item) # #[todo] append sources of first item.
3939+ raise DropItem("Duplicate item found: %s" % item) # [todo] append sources of first item.
3940 else:
4041 self.known_values.add(value)
4142 return item
4343+42444345class AttributeSelectionPipeline(object):
4444-4546 def __init__(self):
4646- pass;
4747+ pass
47484848- def process_item(self, item, spider):
4949+ @staticmethod
5050+ def process_item(item, spider):
4951 """
5052 The items are processed using the selected attribute list available in the spider,
5153 items that don't match the selected items are dropped.
+1-1
FourmiCrawler/settings.py
···33# For simplicity, this file contains only the most important settings by
44# default. All the other settings are documented here:
55#
66-# http://doc.scrapy.org/en/latest/topics/settings.html
66+# http://doc.scrapy.org/en/latest/topics/settings.html
77#
8899BOT_NAME = 'FourmiCrawler'
+6-5
FourmiCrawler/sources/ChemSpider.py
···11-from source import Source
11+import re
22+23from scrapy import log
34from scrapy.http import Request
45from scrapy.selector import Selector
66+77+from source import Source
58from FourmiCrawler.items import Result
66-import re
99+710811# [TODO] - Maybe clean up usage of '.extract()[0]', because of possible IndexError exception.
912···5861 prop_conditions = ''
59626063 # Test for properties without values, with one hardcoded exception
6161- if (not re.match(r'^\d', prop_value) or
6262- (prop_name == 'Polarizability' and
6363- prop_value == '10-24cm3')):
6464+ if not re.match(r'^\d', prop_value) or (prop_name == 'Polarizability' and prop_value == '10-24cm3'):
6465 continue
65666667 # Match for condition in parentheses
+13-10
FourmiCrawler/sources/NIST.py
···11-from source import Source
11+import re
22+23from scrapy import log
34from scrapy.http import Request
45from scrapy.selector import Selector
66+77+from source import Source
58from FourmiCrawler.items import Result
66-import re
99+710811# [TODO]: values can be '128.', perhaps remove the dot in that case?
912# [TODO]: properties have references and comments which do not exist in the
1010-# Result item, but should be included eventually.
1313+# Result item, but should be included eventually.
11141215class NIST(Source):
1316 """NIST Scraper plugin
···1518 This plugin manages searching for a chemical on the NIST website
1619 and parsing the resulting page if the chemical exists on NIST.
1720 """
1818- website = "http://webbook.nist.gov/*"
2121+ website = "http://webbook.nist.gov/*"
19222023 search = 'cgi/cbook.cgi?Name=%s&Units=SI&cTP=on'
2124···7578 requests.extend(self.parse_generic_data(table, summary))
7679 else:
7780 log.msg('NIST table: NOT SUPPORTED', level=log.WARNING)
7878- continue #Assume unsupported
8181+ continue # Assume unsupported
7982 return requests
80838184 def parse_generic_info(self, sel):
···103106 data['IUPAC Standard InChI'] = raw_inchi.extract()[0]
104107105108 raw_inchikey = ul.xpath('li[strong="IUPAC Standard InChIKey:"]'
106106- '/tt/text()')
109109+ '/tt/text()')
107110 data['IUPAC Standard InChIKey'] = raw_inchikey.extract()[0]
108111109112 raw_cas_number = ul.xpath('li[strong="CAS Registry Number:"]/text()')
···129132 results = []
130133 for tr in table.xpath('tr[td]'):
131134 extra_data_url = tr.xpath('td[last()][a="Individual data points"]'
132132- '/a/@href').extract()
135135+ '/a/@href').extract()
133136 if extra_data_url:
134137 request = Request(url=self.website[:-1] + extra_data_url[0],
135135- callback=self.parse_individual_datapoints)
138138+ callback=self.parse_individual_datapoints)
136139 results.append(request)
137140 continue
138141 data = []
···179182 'conditions': '%s K, (%s -> %s)' % (tds[1], tds[2], tds[3])
180183 })
181184 results.append(result)
182182-183185184186 return results
185187···228230229231 return results
230232231231- def parse_individual_datapoints(self, response):
233233+ @staticmethod
234234+ def parse_individual_datapoints(response):
232235 """Parses the page linked from aggregate data"""
233236 sel = Selector(response)
234237 table = sel.xpath('//table[@class="data"]')[0]
+5-3
FourmiCrawler/sources/WikipediaParser.py
···11+import re
22+13from scrapy.http import Request
24from scrapy import log
33-from source import Source
45from scrapy.selector import Selector
66+77+from source import Source
58from FourmiCrawler.items import Result
66-import re
79810911class WikipediaParser(Source):
···3638 """ scrape data from infobox on wikipedia. """
3739 items = []
38403939- #be sure to get chembox (wikipedia template)
4141+ # be sure to get chembox (wikipedia template)
4042 tr_list = sel.xpath('.//table[@class="infobox bordered"]//td[not(@colspan)]'). \
4143 xpath('normalize-space(string())')
4244 prop_names = tr_list[::2]
+12-10
FourmiCrawler/spider.py
···99 A spider writen for the Fourmi Project which calls upon all available sources to request and scrape data.
1010 """
1111 name = "FourmiSpider"
1212- __sources = []
1313- synonyms = []
1212+ _sources = []
1313+ synonyms = set()
14141515 def __init__(self, compound=None, selected_attributes=[".*"], *args, **kwargs):
1616 """
···1919 :param selected_attributes: A list of regular expressions that the attributes should match.
2020 """
2121 super(FourmiSpider, self).__init__(*args, **kwargs)
2222- self.synonyms.append(compound)
2323- self.selected_attributes = selected_attributes;
2222+ self.synonyms.add(compound)
2323+ self.selected_attributes = selected_attributes
24242525 def parse(self, response):
2626 """
···2929 :param response: A Scrapy Response object that should be parsed
3030 :return: A list of Result items and new Request to be handled by the scrapy core.
3131 """
3232- for source in self.__sources:
3232+ for source in self._sources:
3333 if re.match(source.website, response.url):
3434 log.msg("Url: " + response.url + " -> Source: " + source.website, level=log.DEBUG)
3535 return source.parse(response)
···4242 :return: A list of Scrapy Request objects
4343 """
4444 requests = []
4545- for parser in self.__sources:
4646- parser_requests = parser.new_compound_request(compound)
4747- if parser_requests is not None:
4848- requests.append(parser_requests)
4545+ if compound not in self.synonyms:
4646+ self.synonyms.add(compound)
4747+ for parser in self._sources:
4848+ parser_requests = parser.new_compound_request(compound)
4949+ if parser_requests is not None:
5050+ requests.append(parser_requests)
4951 return requests
50525153 def start_requests(self):
···7173 A function add a new Parser object to the list of available parsers.
7274 :param source: A Source Object
7375 """
7474- self.__sources.append(source)
7676+ self._sources.append(source)
7577 source.set_spider(self)
+4
README.md
···11# Fourmi
2233+**Master branch**: [](https://travis-ci.org/Recondor/Fourmi)
44+55+**Developing branch**: [](https://travis-ci.org/Recondor/Fourmi)
66+37Fourmi is an web scraper for chemical substances. The program is designed to be
48used as a search engine to search multiple chemical databases for a specific
59substance. The program will produce all available attributes of the substance
+1-1
fourmi.py
···11-#!/usr/bin/env python
11+# !/usr/bin/env python
22"""
33Fourmi, a web scraper build to search specific information for a given compound (and it's pseudonyms).
44